Files
third_party_vulkan-loader/scripts/helper_file_generator.py
T
Mark Young 035140a542 Update loader to include handle validation
Validate the Vulkan dispatchable handles (VkInstance, VkPhysicalDevice, etc) for
any trampoline functions the loader uses to query the dispatch table from.
This is so we can at least report errors before something bad happens.

Also, add tests to the test framework to catch this case.  Right now they simply
check to make sure we aborted, but they don't know why the loader aborted.
Eventually, we need to come back and check the loader messages and make sure it
aborted for the reasons we want.

Fix a generator warning in dispatch_table_helper_generator.py where a compare was
the wrong type.

Fixes GH Issue #64.
2021-11-19 10:32:13 -07:00

501 lines
26 KiB
Python

#!/usr/bin/python3 -i
#
# Copyright (c) 2015-2017 The Khronos Group Inc.
# Copyright (c) 2015-2017 Valve Corporation
# Copyright (c) 2015-2017 LunarG, Inc.
# Copyright (c) 2015-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.
#
# Author: Mark Lobodzinski <mark@lunarg.com>
# Author: Tobin Ehlis <tobine@google.com>
# Author: John Zulauf <jzulauf@lunarg.com>
import os,re,sys
import xml.etree.ElementTree as etree
from generator import *
from collections import namedtuple
from common_codegen import *
#
# HelperFileOutputGeneratorOptions - subclass of GeneratorOptions.
class HelperFileOutputGeneratorOptions(GeneratorOptions):
def __init__(self,
conventions = None,
filename = None,
directory = '.',
genpath = None,
apiname = None,
profile = None,
versions = '.*',
emitversions = '.*',
defaultExtensions = None,
addExtensions = None,
removeExtensions = None,
emitExtensions = None,
sortProcedure = regSortFeatures,
prefixText = "",
genFuncPointers = True,
protectFile = True,
protectFeature = True,
apicall = '',
apientry = '',
apientryp = '',
alignFuncParam = 0,
library_name = '',
expandEnumerants = True,
helper_file_type = ''):
GeneratorOptions.__init__(self,
conventions = conventions,
filename = filename,
directory = directory,
genpath = genpath,
apiname = apiname,
profile = profile,
versions = versions,
emitversions = emitversions,
defaultExtensions = defaultExtensions,
addExtensions = addExtensions,
removeExtensions = removeExtensions,
emitExtensions = emitExtensions,
sortProcedure = sortProcedure)
self.prefixText = prefixText
self.genFuncPointers = genFuncPointers
self.protectFile = protectFile
self.protectFeature = protectFeature
self.apicall = apicall
self.apientry = apientry
self.apientryp = apientryp
self.alignFuncParam = alignFuncParam
self.library_name = library_name
self.helper_file_type = helper_file_type
#
# HelperFileOutputGenerator - subclass of OutputGenerator. Outputs Vulkan helper files
class HelperFileOutputGenerator(OutputGenerator):
"""Generate helper file based on XML element attributes"""
def __init__(self,
errFile = sys.stderr,
warnFile = sys.stderr,
diagFile = sys.stdout):
OutputGenerator.__init__(self, errFile, warnFile, diagFile)
# Internal state - accumulators for different inner block text
self.enum_output = '' # string built up of enum string routines
# Internal state - accumulators for different inner block text
self.structNames = [] # List of Vulkan struct typenames
self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType
self.structMembers = [] # List of StructMemberData records for all Vulkan structs
self.object_types = [] # List of all handle types
self.object_type_aliases = [] # Aliases to handles types (for handles that were extensions)
self.debug_report_object_types = [] # Handy copy of debug_report_object_type enum data
self.core_object_types = [] # Handy copy of core_object_type enum data
self.device_extension_info = dict() # Dict of device extension name defines and ifdef values
self.instance_extension_info = dict() # Dict of instance extension name defines and ifdef values
# Named tuples to store struct and command data
self.StructType = namedtuple('StructType', ['name', 'value'])
self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isstaticarray', 'isconst', 'iscount', 'len', 'extstructs', 'cdecl'])
self.StructMemberData = namedtuple('StructMemberData', ['name', 'members', 'ifdef_protect'])
self.custom_construct_params = {
# safe_VkGraphicsPipelineCreateInfo needs to know if subpass has color and\or depth\stencil attachments to use its pointers
'VkGraphicsPipelineCreateInfo' :
', const bool uses_color_attachment, const bool uses_depthstencil_attachment',
# safe_VkPipelineViewportStateCreateInfo needs to know if viewport and scissor is dynamic to use its pointers
'VkPipelineViewportStateCreateInfo' :
', const bool is_dynamic_viewports, const bool is_dynamic_scissors',
}
#
# Called once at the beginning of each run
def beginFile(self, genOpts):
OutputGenerator.beginFile(self, genOpts)
# User-supplied prefix text, if any (list of strings)
self.helper_file_type = genOpts.helper_file_type
self.library_name = genOpts.library_name
# File Comment
file_comment = '// *** THIS FILE IS GENERATED - DO NOT EDIT ***\n'
file_comment += '// See helper_file_generator.py for modifications\n'
write(file_comment, file=self.outFile)
# Copyright Notice
copyright = ''
copyright += '\n'
copyright += '/***************************************************************************\n'
copyright += ' *\n'
copyright += ' * Copyright (c) 2015-2017 The Khronos Group Inc.\n'
copyright += ' * Copyright (c) 2015-2017 Valve Corporation\n'
copyright += ' * Copyright (c) 2015-2017 LunarG, Inc.\n'
copyright += ' * Copyright (c) 2015-2017 Google Inc.\n'
copyright += ' *\n'
copyright += ' * Licensed under the Apache License, Version 2.0 (the "License");\n'
copyright += ' * you may not use this file except in compliance with the License.\n'
copyright += ' * You may obtain a copy of the License at\n'
copyright += ' *\n'
copyright += ' * http://www.apache.org/licenses/LICENSE-2.0\n'
copyright += ' *\n'
copyright += ' * Unless required by applicable law or agreed to in writing, software\n'
copyright += ' * distributed under the License is distributed on an "AS IS" BASIS,\n'
copyright += ' * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n'
copyright += ' * See the License for the specific language governing permissions and\n'
copyright += ' * limitations under the License.\n'
copyright += ' *\n'
copyright += ' * Author: Mark Lobodzinski <mark@lunarg.com>\n'
copyright += ' * Author: Courtney Goeltzenleuchter <courtneygo@google.com>\n'
copyright += ' * Author: Tobin Ehlis <tobine@google.com>\n'
copyright += ' * Author: Chris Forbes <chrisforbes@google.com>\n'
copyright += ' * Author: John Zulauf<jzulauf@lunarg.com>\n'
copyright += ' *\n'
copyright += ' ****************************************************************************/\n'
write(copyright, file=self.outFile)
#
# Write generated file content to output file
def endFile(self):
dest_file = ''
dest_file += self.OutputDestFile()
# Remove blank lines at EOF
if dest_file.endswith('\n'):
dest_file = dest_file[:-1]
write(dest_file, file=self.outFile);
# Finish processing in superclass
OutputGenerator.endFile(self)
#
# Override parent class to be notified of the beginning of an extension
def beginFeature(self, interface, emit):
# Start processing in superclass
OutputGenerator.beginFeature(self, interface, emit)
self.featureExtraProtect = GetFeatureProtect(interface)
if interface.tag != 'extension':
return
name = self.featureName
nameElem = interface[0][1]
name_define = nameElem.get('name')
if 'EXTENSION_NAME' not in name_define:
print("Error in vk.xml file -- extension name is not available")
requires = interface.get('requires')
if requires is not None:
required_extensions = requires.split(',')
else:
required_extensions = list()
info = { 'define': name_define, 'ifdef':self.featureExtraProtect, 'reqs':required_extensions }
if interface.get('type') == 'instance':
self.instance_extension_info[name] = info
else:
self.device_extension_info[name] = info
#
# Override parent class to be notified of the end of an extension
def endFeature(self):
# Finish processing in superclass
OutputGenerator.endFeature(self)
#
# Grab group (e.g. C "enum" type) info to output for enum-string conversion helper
def genGroup(self, groupinfo, groupName, alias):
OutputGenerator.genGroup(self, groupinfo, groupName, alias)
groupElem = groupinfo.elem
# For enum_string_header
if self.helper_file_type == 'enum_string_header':
value_set = set()
for elem in groupElem.findall('enum'):
if elem.get('supported') != 'disabled' and elem.get('alias') is None:
value_set.add(elem.get('name'))
self.enum_output += self.GenerateEnumStringConversion(groupName, value_set)
elif self.helper_file_type == 'object_types_header':
if groupName == 'VkDebugReportObjectTypeEXT':
for elem in groupElem.findall('enum'):
if elem.get('supported') != 'disabled':
item_name = elem.get('name')
self.debug_report_object_types.append(item_name)
elif groupName == 'VkObjectType':
for elem in groupElem.findall('enum'):
if elem.get('supported') != 'disabled':
item_name = elem.get('name')
self.core_object_types.append(item_name)
#
# Called for each type -- if the type is a struct/union, grab the metadata
def genType(self, typeinfo, name, alias):
OutputGenerator.genType(self, typeinfo, name, alias)
typeElem = typeinfo.elem
# If the type is a struct type, traverse the imbedded <member> tags generating a structure.
# Otherwise, emit the tag text.
category = typeElem.get('category')
if category == 'handle':
if alias:
self.object_type_aliases.append((name,alias))
else:
self.object_types.append(name)
elif (category == 'struct' or category == 'union'):
self.structNames.append(name)
self.genStruct(typeinfo, name, alias)
#
# Generate a VkStructureType based on a structure typename
def genVkStructureType(self, typename):
# Add underscore between lowercase then uppercase
value = re.sub('([a-z0-9])([A-Z])', r'\1_\2', typename)
# Change to uppercase
value = value.upper()
# Add STRUCTURE_TYPE_
return re.sub('VK_', 'VK_STRUCTURE_TYPE_', value)
#
# Check if the parameter passed in is a pointer
def paramIsPointer(self, param):
ispointer = False
for elem in param:
if ((elem.tag != 'type') and (elem.tail is not None)) and '*' in elem.tail:
ispointer = True
return ispointer
#
# Check if the parameter passed in is a static array
def paramIsStaticArray(self, param):
isstaticarray = 0
paramname = param.find('name')
if (paramname.tail is not None) and ('[' in paramname.tail):
isstaticarray = paramname.tail.count('[')
return isstaticarray
#
# Retrieve the type and name for a parameter
def getTypeNameTuple(self, param):
type = ''
name = ''
for elem in param:
if elem.tag == 'type':
type = noneStr(elem.text)
elif elem.tag == 'name':
name = noneStr(elem.text)
return (type, name)
#
# Retrieve the value of the len tag
def getLen(self, param):
result = None
len = param.attrib.get('len')
if len and len != 'null-terminated':
# For string arrays, 'len' can look like 'count,null-terminated', indicating that we
# have a null terminated array of strings. We strip the null-terminated from the
# 'len' field and only return the parameter specifying the string count
if 'null-terminated' in len:
result = len.split(',')[0]
else:
result = len
if 'altlen' in param.attrib:
# Elements with latexmath 'len' also contain a C equivalent 'altlen' attribute
# Use indexing operator instead of get() so we fail if the attribute is missing
result = param.attrib['altlen']
# Spec has now notation for len attributes, using :: instead of platform specific pointer symbol
result = str(result).replace('::', '->')
return result
#
# Check if a structure is or contains a dispatchable (dispatchable = True) or
# non-dispatchable (dispatchable = False) handle
def TypeContainsObjectHandle(self, handle_type, dispatchable):
if dispatchable:
type_key = 'VK_DEFINE_HANDLE'
else:
type_key = 'VK_DEFINE_NON_DISPATCHABLE_HANDLE'
handle = self.registry.tree.find("types/type/[name='" + handle_type + "'][@category='handle']")
if handle is not None and handle.find('type').text == type_key:
return True
# if handle_type is a struct, search its members
if handle_type in self.structNames:
member_index = next((i for i, v in enumerate(self.structMembers) if v[0] == handle_type), None)
if member_index is not None:
for item in self.structMembers[member_index].members:
handle = self.registry.tree.find("types/type/[name='" + item.type + "'][@category='handle']")
if handle is not None and handle.find('type').text == type_key:
return True
return False
#
# Generate local ready-access data describing Vulkan structures and unions from the XML metadata
def genStruct(self, typeinfo, typeName, alias):
OutputGenerator.genStruct(self, typeinfo, typeName, alias)
members = typeinfo.elem.findall('.//member')
# Iterate over members once to get length parameters for arrays
lens = set()
for member in members:
len = self.getLen(member)
if len:
lens.add(len)
# Generate member info
membersInfo = []
for member in members:
# Get the member's type and name
info = self.getTypeNameTuple(member)
type = info[0]
name = info[1]
cdecl = self.makeCParamDecl(member, 1)
# Process VkStructureType
if type == 'VkStructureType':
# Extract the required struct type value from the comments
# embedded in the original text defining the 'typeinfo' element
rawXml = etree.tostring(typeinfo.elem).decode('ascii')
result = re.search(r'VK_STRUCTURE_TYPE_\w+', rawXml)
if result:
value = result.group(0)
else:
value = self.genVkStructureType(typeName)
# Store the required type value
self.structTypes[typeName] = self.StructType(name=name, value=value)
# Store pointer/array/string info
isstaticarray = self.paramIsStaticArray(member)
membersInfo.append(self.CommandParam(type=type,
name=name,
ispointer=self.paramIsPointer(member),
isstaticarray=isstaticarray,
isconst=True if 'const' in cdecl else False,
iscount=True if name in lens else False,
len=self.getLen(member),
extstructs=self.registry.validextensionstructs[typeName] if name == 'pNext' else None,
cdecl=cdecl))
self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo, ifdef_protect=self.featureExtraProtect))
#
# Enum_string_header: Create a routine to convert an enumerated value into a string
def GenerateEnumStringConversion(self, groupName, value_list):
outstring = '\n'
outstring += 'static inline const char* string_%s(%s input_value)\n' % (groupName, groupName)
outstring += '{\n'
outstring += ' switch ((%s)input_value)\n' % groupName
outstring += ' {\n'
for item in value_list:
outstring += ' case %s:\n' % item
outstring += ' return "%s";\n' % item
outstring += ' default:\n'
outstring += ' return "Unhandled %s";\n' % groupName
outstring += ' }\n'
outstring += '}\n'
return outstring
#
# Combine object types helper header file preamble with body text and return
def GenerateObjectTypesHelperHeader(self):
object_types_helper_header = '\n'
object_types_helper_header += '#pragma once\n'
object_types_helper_header += '\n'
object_types_helper_header += '#include <vulkan/vulkan.h>\n\n'
object_types_helper_header += self.GenerateObjectTypesHeader()
return object_types_helper_header
#
# Object types header: create object enum type header file
def GenerateObjectTypesHeader(self):
object_types_header = ''
object_types_header += '// Object Type enum for validation layer internal object handling\n'
object_types_header += 'typedef enum VulkanObjectType {\n'
object_types_header += ' kVulkanObjectTypeUnknown = 0,\n'
enum_num = 1
type_list = [];
enum_entry_map = {}
# Output enum definition as each handle is processed, saving the names to use for the conversion routine
for item in self.object_types:
fixup_name = item[2:]
enum_entry = 'kVulkanObjectType%s' % fixup_name
enum_entry_map[item] = enum_entry
object_types_header += ' ' + enum_entry
object_types_header += ' = %d,\n' % enum_num
enum_num += 1
type_list.append(enum_entry)
object_types_header += ' kVulkanObjectTypeMax = %d,\n' % enum_num
object_types_header += ' // Aliases for backwards compatibilty of "promoted" types\n'
for (name, alias) in self.object_type_aliases:
fixup_name = name[2:]
object_types_header += ' kVulkanObjectType{} = {},\n'.format(fixup_name, enum_entry_map[alias])
object_types_header += '} VulkanObjectType;\n\n'
# Output name string helper
object_types_header += '// Array of object name strings for OBJECT_TYPE enum conversion\n'
object_types_header += 'static const char * const object_string[kVulkanObjectTypeMax] = {\n'
object_types_header += ' "Unknown",\n'
for item in self.object_types:
fixup_name = item[2:]
object_types_header += ' "%s",\n' % fixup_name
object_types_header += '};\n'
# Key creation helper for map comprehensions that convert between k<Name> and VK<Name> symbols
def to_key(regex, raw_key): return re.search(regex, raw_key).group(1).lower().replace("_","")
# Output a conversion routine from the layer object definitions to the debug report definitions
# As the VK_DEBUG_REPORT types are not being updated, specify UNKNOWN for unmatched types
object_types_header += '\n'
object_types_header += '// Helper array to get Vulkan VK_EXT_debug_report object type enum from the internal layers version\n'
object_types_header += 'const VkDebugReportObjectTypeEXT get_debug_report_enum[] = {\n'
object_types_header += ' VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, // kVulkanObjectTypeUnknown\n'
dbg_re = '^VK_DEBUG_REPORT_OBJECT_TYPE_(.*)_EXT$'
dbg_map = {to_key(dbg_re, dbg) : dbg for dbg in self.debug_report_object_types}
dbg_default = 'VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT'
for object_type in type_list:
vk_object_type = dbg_map.get(object_type.replace("kVulkanObjectType", "").lower(), dbg_default)
object_types_header += ' %s, // %s\n' % (vk_object_type, object_type)
object_types_header += '};\n'
# Output a conversion routine from the layer object definitions to the core object type definitions
# This will intentionally *fail* for unmatched types as the VK_OBJECT_TYPE list should match the kVulkanObjectType list
object_types_header += '\n'
object_types_header += '// Helper array to get Official Vulkan VkObjectType enum from the internal layers version\n'
object_types_header += 'const VkObjectType get_object_type_enum[] = {\n'
object_types_header += ' VK_OBJECT_TYPE_UNKNOWN, // kVulkanObjectTypeUnknown\n'
vko_re = '^VK_OBJECT_TYPE_(.*)'
vko_map = {to_key(vko_re, vko) : vko for vko in self.core_object_types}
for object_type in type_list:
vk_object_type = vko_map[object_type.replace("kVulkanObjectType", "").lower()]
object_types_header += ' %s, // %s\n' % (vk_object_type, object_type)
object_types_header += '};\n'
# Create a function to convert from VkDebugReportObjectTypeEXT to VkObjectType
object_types_header += '\n'
object_types_header += '// Helper function to convert from VkDebugReportObjectTypeEXT to VkObjectType\n'
object_types_header += 'static inline VkObjectType convertDebugReportObjectToCoreObject(VkDebugReportObjectTypeEXT debug_report_obj){\n'
object_types_header += ' if (debug_report_obj == VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT) {\n'
object_types_header += ' return VK_OBJECT_TYPE_UNKNOWN;\n'
for core_object_type in self.core_object_types:
core_target_type = core_object_type.replace("VK_OBJECT_TYPE_", "").lower()
core_target_type = core_target_type.replace("_", "")
for dr_object_type in self.debug_report_object_types:
dr_target_type = dr_object_type.replace("VK_DEBUG_REPORT_OBJECT_TYPE_", "").lower()
dr_target_type = dr_target_type[:-4]
dr_target_type = dr_target_type.replace("_", "")
if core_target_type == dr_target_type:
object_types_header += ' } else if (debug_report_obj == %s) {\n' % dr_object_type
object_types_header += ' return %s;\n' % core_object_type
break
object_types_header += ' }\n'
object_types_header += ' return VK_OBJECT_TYPE_UNKNOWN;\n'
object_types_header += '}\n'
# Create a function to convert from VkObjectType to VkDebugReportObjectTypeEXT
object_types_header += '\n'
object_types_header += '// Helper function to convert from VkDebugReportObjectTypeEXT to VkObjectType\n'
object_types_header += 'static inline VkDebugReportObjectTypeEXT convertCoreObjectToDebugReportObject(VkObjectType core_report_obj){\n'
object_types_header += ' if (core_report_obj == VK_OBJECT_TYPE_UNKNOWN) {\n'
object_types_header += ' return VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT;\n'
for core_object_type in self.core_object_types:
core_target_type = core_object_type.replace("VK_OBJECT_TYPE_", "").lower()
core_target_type = core_target_type.replace("_", "")
for dr_object_type in self.debug_report_object_types:
dr_target_type = dr_object_type.replace("VK_DEBUG_REPORT_OBJECT_TYPE_", "").lower()
dr_target_type = dr_target_type[:-4]
dr_target_type = dr_target_type.replace("_", "")
if core_target_type == dr_target_type:
object_types_header += ' } else if (core_report_obj == %s) {\n' % core_object_type
object_types_header += ' return %s;\n' % dr_object_type
break
object_types_header += ' }\n'
object_types_header += ' return VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT;\n'
object_types_header += '}\n'
return object_types_header
#
# Create a helper file and return it as a string
def OutputDestFile(self):
if self.helper_file_type == 'object_types_header':
return self.GenerateObjectTypesHelperHeader()
else:
return 'Bad Helper File Generator Option %s' % self.helper_file_type