From 0fcd1a7ef1f62557daa83f390bbaf4ab40465ddd Mon Sep 17 00:00:00 2001 From: Mark Lobodzinski Date: Fri, 18 Nov 2016 14:58:57 -0700 Subject: build: Move XML codegen scripts into scripts dir - Update android-generate.bat and android-generate.sh - Updated layer CMakeLists.txt files for new path Change-Id: I1b1c9dbc9e944c90e95542fcfdda1d2e804517f3 --- build-android/android-generate.bat | 6 +- build-android/android-generate.sh | 6 +- generator.py | 498 --------------- layers/CMakeLists.txt | 4 +- lvl_genvk.py | 275 --------- parameter_validation_generator.py | 987 ------------------------------ reg.py | 813 ------------------------ scripts/generator.py | 498 +++++++++++++++ scripts/lvl_genvk.py | 275 +++++++++ scripts/parameter_validation_generator.py | 987 ++++++++++++++++++++++++++++++ scripts/reg.py | 813 ++++++++++++++++++++++++ scripts/threading_generator.py | 467 ++++++++++++++ scripts/unique_objects_generator.py | 760 +++++++++++++++++++++++ threading_generator.py | 467 -------------- unique_objects_generator.py | 760 ----------------------- 15 files changed, 3808 insertions(+), 3808 deletions(-) delete mode 100755 generator.py delete mode 100644 lvl_genvk.py delete mode 100644 parameter_validation_generator.py delete mode 100755 reg.py create mode 100755 scripts/generator.py create mode 100644 scripts/lvl_genvk.py create mode 100644 scripts/parameter_validation_generator.py create mode 100755 scripts/reg.py create mode 100644 scripts/threading_generator.py create mode 100644 scripts/unique_objects_generator.py delete mode 100644 threading_generator.py delete mode 100644 unique_objects_generator.py diff --git a/build-android/android-generate.bat b/build-android/android-generate.bat index 45dd18fb..96491e1a 100644 --- a/build-android/android-generate.bat +++ b/build-android/android-generate.bat @@ -25,9 +25,9 @@ python ../scripts/vk_helper.py --gen_enum_string_helper ../include/vulkan/vulkan python ../scripts/vk_helper.py --gen_struct_wrappers ../include/vulkan/vulkan.h --abs_out_dir generated/include cd generated/include -python ../../../lvl_genvk.py -registry ../../../vk.xml thread_check.h -python ../../../lvl_genvk.py -registry ../../../vk.xml parameter_validation.h -python ../../../lvl_genvk.py -registry ../../../vk.xml unique_objects_wrappers.h +python ../../../scripts/lvl_genvk.py -registry ../../../vk.xml thread_check.h +python ../../../scripts/lvl_genvk.py -registry ../../../vk.xml parameter_validation.h +python ../../../scripts/lvl_genvk.py -registry ../../../vk.xml unique_objects_wrappers.h cd ../.. copy /Y ..\layers\vk_layer_config.cpp generated\common\ diff --git a/build-android/android-generate.sh b/build-android/android-generate.sh index cdd9ede7..66293fd9 100755 --- a/build-android/android-generate.sh +++ b/build-android/android-generate.sh @@ -26,9 +26,9 @@ python ../scripts/vk-generate.py Android dispatch-table-ops layer > generated/in python ../scripts/vk_helper.py --gen_enum_string_helper ../include/vulkan/vulkan.h --abs_out_dir generated/include python ../scripts/vk_helper.py --gen_struct_wrappers ../include/vulkan/vulkan.h --abs_out_dir generated/include -( cd generated/include; python ../../../lvl_genvk.py -registry ../../../vk.xml thread_check.h ) -( cd generated/include; python ../../../lvl_genvk.py -registry ../../../vk.xml parameter_validation.h ) -( cd generated/include; python ../../../lvl_genvk.py -registry ../../../vk.xml unique_objects_wrappers.h ) +( cd generated/include; python ../../../scripts/lvl_genvk.py -registry ../../../vk.xml thread_check.h ) +( cd generated/include; python ../../../scripts/lvl_genvk.py -registry ../../../vk.xml parameter_validation.h ) +( cd generated/include; python ../../../scripts/lvl_genvk.py -registry ../../../vk.xml unique_objects_wrappers.h ) cp -f ../layers/vk_layer_config.cpp generated/common/ cp -f ../layers/vk_layer_extension_utils.cpp generated/common/ diff --git a/generator.py b/generator.py deleted file mode 100755 index 043121cd..00000000 --- a/generator.py +++ /dev/null @@ -1,498 +0,0 @@ -#!/usr/bin/python3 -i -# -# Copyright (c) 2013-2016 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 os,re,sys - -def write( *args, **kwargs ): - file = kwargs.pop('file',sys.stdout) - end = kwargs.pop( 'end','\n') - file.write( ' '.join([str(arg) for arg in args]) ) - file.write( end ) - -# noneStr - returns string argument, or "" if argument is None. -# Used in converting etree Elements into text. -# str - string to convert -def noneStr(str): - if (str): - return str - else: - return "" - -# enquote - returns string argument with surrounding quotes, -# for serialization into Python code. -def enquote(str): - if (str): - return "'" + str + "'" - else: - return None - -# apiName - returns True if name is a Vulkan name (vk/Vk/VK prefix, or a -# function pointer type), False otherwise. -def apiName(str): - return str[0:2].lower() == 'vk' or str[0:3] == 'PFN' - -# Primary sort key for regSortFeatures. -# Sorts by category of the feature name string: -# Core API features (those defined with a tag) -# ARB/KHR/OES (Khronos extensions) -# other (EXT/vendor extensions) -# This will need changing for Vulkan! -def regSortCategoryKey(feature): - if (feature.elem.tag == 'feature'): - return 0 - elif (feature.category == 'ARB' or - feature.category == 'KHR' or - feature.category == 'OES'): - return 1 - else: - return 2 - -# Secondary sort key for regSortFeatures. -# Sorts by extension name. -def regSortNameKey(feature): - return feature.name - -# Second sort key for regSortFeatures. -# Sorts by feature version. elements all have version number "0" -def regSortFeatureVersionKey(feature): - return float(feature.version) - -# Tertiary sort key for regSortFeatures. -# Sorts by extension number. elements all have extension number 0. -def regSortExtensionNumberKey(feature): - return int(feature.number) - -# regSortFeatures - default sort procedure for features. -# Sorts by primary key of feature category ('feature' or 'extension') -# then by version number (for features) -# then by extension number (for extensions) -def regSortFeatures(featureList): - featureList.sort(key = regSortExtensionNumberKey) - featureList.sort(key = regSortFeatureVersionKey) - featureList.sort(key = regSortCategoryKey) - -# GeneratorOptions - base class for options used during header production -# These options are target language independent, and used by -# Registry.apiGen() and by base OutputGenerator objects. -# -# Members -# filename - basename of file to generate, or None to write to stdout. -# directory - directory in which to generate filename -# apiname - string matching 'apiname' attribute, e.g. 'gl'. -# profile - string specifying API profile , e.g. 'core', or None. -# versions - regex matching API versions to process interfaces for. -# Normally '.*' or '[0-9]\.[0-9]' to match all defined versions. -# emitversions - regex matching API versions to actually emit -# interfaces for (though all requested versions are considered -# when deciding which interfaces to generate). For GL 4.3 glext.h, -# this might be '1\.[2-5]|[2-4]\.[0-9]'. -# defaultExtensions - If not None, a string which must in its -# entirety match the pattern in the "supported" attribute of -# the . Defaults to None. Usually the same as apiname. -# addExtensions - regex matching names of additional extensions -# to include. Defaults to None. -# removeExtensions - regex matching names of extensions to -# remove (after defaultExtensions and addExtensions). Defaults -# to None. -# sortProcedure - takes a list of FeatureInfo objects and sorts -# them in place to a preferred order in the generated output. -# Default is core API versions, ARB/KHR/OES extensions, all -# other extensions, alphabetically within each group. -# The regex patterns can be None or empty, in which case they match -# nothing. -class GeneratorOptions: - """Represents options during header production from an API registry""" - def __init__(self, - filename = None, - directory = '.', - apiname = None, - profile = None, - versions = '.*', - emitversions = '.*', - defaultExtensions = None, - addExtensions = None, - removeExtensions = None, - sortProcedure = regSortFeatures): - self.filename = filename - self.directory = directory - self.apiname = apiname - self.profile = profile - self.versions = self.emptyRegex(versions) - self.emitversions = self.emptyRegex(emitversions) - self.defaultExtensions = defaultExtensions - self.addExtensions = self.emptyRegex(addExtensions) - self.removeExtensions = self.emptyRegex(removeExtensions) - self.sortProcedure = sortProcedure - # - # Substitute a regular expression which matches no version - # or extension names for None or the empty string. - def emptyRegex(self,pat): - if (pat == None or pat == ''): - return '_nomatch_^' - else: - return pat - -# OutputGenerator - base class for generating API interfaces. -# Manages basic logic, logging, and output file control -# Derived classes actually generate formatted output. -# -# ---- methods ---- -# OutputGenerator(errFile, warnFile, diagFile) -# errFile, warnFile, diagFile - file handles to write errors, -# warnings, diagnostics to. May be None to not write. -# logMsg(level, *args) - log messages of different categories -# level - 'error', 'warn', or 'diag'. 'error' will also -# raise a UserWarning exception -# *args - print()-style arguments -# setExtMap(map) - specify a dictionary map from extension names to -# numbers, used in creating values for extension enumerants. -# makeDir(directory) - create a directory, if not already done. -# Generally called from derived generators creating hierarchies. -# beginFile(genOpts) - start a new interface file -# genOpts - GeneratorOptions controlling what's generated and how -# endFile() - finish an interface file, closing it when done -# beginFeature(interface, emit) - write interface for a feature -# and tag generated features as having been done. -# interface - element for the / to generate -# emit - actually write to the header only when True -# endFeature() - finish an interface. -# genType(typeinfo,name) - generate interface for a type -# typeinfo - TypeInfo for a type -# genStruct(typeinfo,name) - generate interface for a C "struct" type. -# typeinfo - TypeInfo for a type interpreted as a struct -# genGroup(groupinfo,name) - generate interface for a group of enums (C "enum") -# groupinfo - GroupInfo for a group -# genEnum(enuminfo, name) - generate interface for an enum (constant) -# enuminfo - EnumInfo for an enum -# name - enum name -# genCmd(cmdinfo) - generate interface for a command -# cmdinfo - CmdInfo for a command -# isEnumRequired(enumElem) - return True if this element is required -# elem - element to test -# makeCDecls(cmd) - return C prototype and function pointer typedef for a -# Element, as a list of two strings -# cmd - Element for the -# newline() - print a newline to the output file (utility function) -# -class OutputGenerator: - """Generate specified API interfaces in a specific style, such as a C header""" - # - # categoryToPath - map XML 'category' to include file directory name - categoryToPath = { - 'bitmask' : 'flags', - 'enum' : 'enums', - 'funcpointer' : 'funcpointers', - 'handle' : 'handles', - 'define' : 'defines', - 'basetype' : 'basetypes', - } - # - # Constructor - def __init__(self, - errFile = sys.stderr, - warnFile = sys.stderr, - diagFile = sys.stdout): - self.outFile = None - self.errFile = errFile - self.warnFile = warnFile - self.diagFile = diagFile - # Internal state - self.featureName = None - self.genOpts = None - self.registry = None - # Used for extension enum value generation - self.extBase = 1000000000 - self.extBlockSize = 1000 - self.madeDirs = {} - # - # logMsg - write a message of different categories to different - # destinations. - # level - - # 'diag' (diagnostic, voluminous) - # 'warn' (warning) - # 'error' (fatal error - raises exception after logging) - # *args - print()-style arguments to direct to corresponding log - def logMsg(self, level, *args): - """Log a message at the given level. Can be ignored or log to a file""" - if (level == 'error'): - strfile = io.StringIO() - write('ERROR:', *args, file=strfile) - if (self.errFile != None): - write(strfile.getvalue(), file=self.errFile) - raise UserWarning(strfile.getvalue()) - elif (level == 'warn'): - if (self.warnFile != None): - write('WARNING:', *args, file=self.warnFile) - elif (level == 'diag'): - if (self.diagFile != None): - write('DIAG:', *args, file=self.diagFile) - else: - raise UserWarning( - '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) - # - # enumToValue - parses and converts an tag into a value. - # Returns a list - # first element - integer representation of the value, or None - # if needsNum is False. The value must be a legal number - # if needsNum is True. - # second element - string representation of the value - # There are several possible representations of values. - # A 'value' attribute simply contains the value. - # A 'bitpos' attribute defines a value by specifying the bit - # position which is set in that value. - # A 'offset','extbase','extends' triplet specifies a value - # as an offset to a base value defined by the specified - # 'extbase' extension name, which is then cast to the - # typename specified by 'extends'. This requires probing - # the registry database, and imbeds knowledge of the - # Vulkan extension enum scheme in this function. - def enumToValue(self, elem, needsNum): - name = elem.get('name') - numVal = None - if ('value' in elem.keys()): - value = elem.get('value') - # print('About to translate value =', value, 'type =', type(value)) - if (needsNum): - numVal = int(value, 0) - # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or - # 'ull'), append it to the string value. - # t = enuminfo.elem.get('type') - # if (t != None and t != '' and t != 'i' and t != 's'): - # value += enuminfo.type - self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']') - return [numVal, value] - if ('bitpos' in elem.keys()): - value = elem.get('bitpos') - numVal = int(value, 0) - numVal = 1 << numVal - value = '0x%08x' % numVal - self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') - return [numVal, value] - if ('offset' in elem.keys()): - # Obtain values in the mapping from the attributes - enumNegative = False - offset = int(elem.get('offset'),0) - extnumber = int(elem.get('extnumber'),0) - extends = elem.get('extends') - if ('dir' in elem.keys()): - enumNegative = True - self.logMsg('diag', 'Enum', name, 'offset =', offset, - 'extnumber =', extnumber, 'extends =', extends, - 'enumNegative =', enumNegative) - # Now determine the actual enumerant value, as defined - # in the "Layers and Extensions" appendix of the spec. - numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset - if (enumNegative): - numVal = -numVal - value = '%d' % numVal - # More logic needed! - self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') - return [numVal, value] - return [None, None] - # - def makeDir(self, path): - self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') - if not (path in self.madeDirs.keys()): - # This can get race conditions with multiple writers, see - # https://stackoverflow.com/questions/273192/ - if not os.path.exists(path): - os.makedirs(path) - self.madeDirs[path] = None - # - def beginFile(self, genOpts): - self.genOpts = genOpts - # - # Open specified output file. Not done in constructor since a - # Generator can be used without writing to a file. - if (self.genOpts.filename != None): - self.outFile = open(self.genOpts.directory + '/' + self.genOpts.filename, 'w') - else: - self.outFile = sys.stdout - def endFile(self): - self.errFile and self.errFile.flush() - self.warnFile and self.warnFile.flush() - self.diagFile and self.diagFile.flush() - self.outFile.flush() - if (self.outFile != sys.stdout and self.outFile != sys.stderr): - self.outFile.close() - self.genOpts = None - # - def beginFeature(self, interface, emit): - self.emit = emit - self.featureName = interface.get('name') - # If there's an additional 'protect' attribute in the feature, save it - self.featureExtraProtect = interface.get('protect') - def endFeature(self): - # Derived classes responsible for emitting feature - self.featureName = None - self.featureExtraProtect = None - # Utility method to validate we're generating something only inside a - # tag - def validateFeature(self, featureType, featureName): - if (self.featureName == None): - raise UserWarning('Attempt to generate', featureType, name, - 'when not in feature') - # - # Type generation - def genType(self, typeinfo, name): - self.validateFeature('type', name) - # - # Struct (e.g. C "struct" type) generation - def genStruct(self, typeinfo, name): - self.validateFeature('struct', name) - # - # Group (e.g. C "enum" type) generation - def genGroup(self, groupinfo, name): - self.validateFeature('group', name) - # - # Enumerant (really, constant) generation - def genEnum(self, enuminfo, name): - self.validateFeature('enum', name) - # - # Command generation - def genCmd(self, cmd, name): - self.validateFeature('command', name) - # - # Utility functions - turn a into C-language prototype - # and typedef declarations for that name. - # name - contents of tag - # tail - whatever text follows that tag in the Element - def makeProtoName(self, name, tail): - return self.genOpts.apientry + name + tail - def makeTypedefName(self, name, tail): - return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' - # - # makeCParamDecl - return a string which is an indented, formatted - # declaration for a or block (e.g. function parameter - # or structure/union member). - # param - Element ( or ) to format - # aligncol - if non-zero, attempt to align the nested element - # at this column - def makeCParamDecl(self, param, aligncol): - paramdecl = ' ' + noneStr(param.text) - for elem in param: - text = noneStr(elem.text) - tail = noneStr(elem.tail) - if (elem.tag == 'name' and aligncol > 0): - self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) - # Align at specified column, if possible - paramdecl = paramdecl.rstrip() - oldLen = len(paramdecl) - # This works around a problem where very long type names - - # longer than the alignment column - would run into the tail - # text. - paramdecl = paramdecl.ljust(aligncol-1) + ' ' - newLen = len(paramdecl) - self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) - paramdecl += text + tail - return paramdecl - # - # getCParamTypeLength - return the length of the type field is an indented, formatted - # declaration for a or block (e.g. function parameter - # or structure/union member). - # param - Element ( or ) to identify - def getCParamTypeLength(self, param): - paramdecl = ' ' + noneStr(param.text) - for elem in param: - text = noneStr(elem.text) - tail = noneStr(elem.tail) - if (elem.tag == 'name'): - # Align at specified column, if possible - newLen = len(paramdecl.rstrip()) - self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) - paramdecl += text + tail - return newLen - # - # isEnumRequired(elem) - return True if this element is - # required, False otherwise - # elem - element to test - def isEnumRequired(self, elem): - return (elem.get('extname') is None or - re.match(self.genOpts.addExtensions, elem.get('extname')) is not None or - self.genOpts.defaultExtensions == elem.get('supported')) - # - # makeCDecls - return C prototype and function pointer typedef for a - # command, as a two-element list of strings. - # cmd - Element containing a tag - def makeCDecls(self, cmd): - """Generate C function pointer typedef for Element""" - proto = cmd.find('proto') - params = cmd.findall('param') - # Begin accumulating prototype and typedef strings - pdecl = self.genOpts.apicall - tdecl = 'typedef ' - # - # Insert the function return type/name. - # For prototypes, add APIENTRY macro before the name - # For typedefs, add (APIENTRY *) around the name and - # use the PFN_cmdnameproc naming convention. - # Done by walking the tree for element by element. - # etree has elem.text followed by (elem[i], elem[i].tail) - # for each child element and any following text - # Leading text - pdecl += noneStr(proto.text) - tdecl += noneStr(proto.text) - # For each child element, if it's a wrap in appropriate - # declaration. Otherwise append its contents and tail contents. - for elem in proto: - text = noneStr(elem.text) - tail = noneStr(elem.tail) - if (elem.tag == 'name'): - pdecl += self.makeProtoName(text, tail) - tdecl += self.makeTypedefName(text, tail) - else: - pdecl += text + tail - tdecl += text + tail - # Now add the parameter declaration list, which is identical - # for prototypes and typedefs. Concatenate all the text from - # a node without the tags. No tree walking required - # since all tags are ignored. - # Uses: self.indentFuncProto - # self.indentFuncPointer - # self.alignFuncParam - # Might be able to doubly-nest the joins, e.g. - # ','.join(('_'.join([l[i] for i in range(0,len(l))]) - n = len(params) - # Indented parameters - if n > 0: - indentdecl = '(\n' - for i in range(0,n): - paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam) - if (i < n - 1): - paramdecl += ',\n' - else: - paramdecl += ');' - indentdecl += paramdecl - else: - indentdecl = '(void);' - # Non-indented parameters - paramdecl = '(' - if n > 0: - for i in range(0,n): - paramdecl += ''.join([t for t in params[i].itertext()]) - if (i < n - 1): - paramdecl += ', ' - else: - paramdecl += 'void' - paramdecl += ");"; - return [ pdecl + indentdecl, tdecl + paramdecl ] - # - def newline(self): - write('', file=self.outFile) - - def setRegistry(self, registry): - self.registry = registry - # diff --git a/layers/CMakeLists.txt b/layers/CMakeLists.txt index 66d25898..fa95fd33 100644 --- a/layers/CMakeLists.txt +++ b/layers/CMakeLists.txt @@ -9,8 +9,8 @@ endmacro() macro(run_vk_layer_xml_generate dependency output) add_custom_command(OUTPUT ${output} - COMMAND ${PYTHON_CMD} ${PROJECT_SOURCE_DIR}/lvl_genvk.py -registry ${PROJECT_SOURCE_DIR}/vk.xml ${output} - DEPENDS ${PROJECT_SOURCE_DIR}/vk.xml ${PROJECT_SOURCE_DIR}/generator.py ${PROJECT_SOURCE_DIR}/${dependency} ${PROJECT_SOURCE_DIR}/lvl_genvk.py ${PROJECT_SOURCE_DIR}/reg.py + COMMAND ${PYTHON_CMD} ${SCRIPTS_DIR}/lvl_genvk.py -registry ${PROJECT_SOURCE_DIR}/vk.xml ${output} + DEPENDS ${PROJECT_SOURCE_DIR}/vk.xml ${SCRIPTS_DIR}/generator.py ${SCRIPTS_DIR}/${dependency} ${SCRIPTS_DIR}/lvl_genvk.py ${SCRIPTS_DIR}/reg.py ) endmacro() diff --git a/lvl_genvk.py b/lvl_genvk.py deleted file mode 100644 index 8fa7c1bf..00000000 --- a/lvl_genvk.py +++ /dev/null @@ -1,275 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2013-2016 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 argparse, cProfile, pdb, string, sys, time -from reg import * -from generator import write - -# -# LoaderAndValidationLayer Generator Additions -from threading_generator import ThreadGeneratorOptions, ThreadOutputGenerator -from parameter_validation_generator import ParamCheckerGeneratorOptions, ParamCheckerOutputGenerator -from unique_objects_generator import UniqueObjectsGeneratorOptions, UniqueObjectsOutputGenerator - -# Simple timer functions -startTime = None - -def startTimer(timeit): - global startTime - startTime = time.clock() - -def endTimer(timeit, msg): - global startTime - endTime = time.clock() - if (timeit): - write(msg, endTime - startTime, file=sys.stderr) - startTime = None - -# Turn a list of strings into a regexp string matching exactly those strings -def makeREstring(list): - return '^(' + '|'.join(list) + ')$' - -# Returns a directory of [ generator function, generator options ] indexed -# by specified short names. The generator options incorporate the following -# parameters: -# -# extensions - list of extension names to include. -# protect - True if re-inclusion protection should be added to headers -# directory - path to directory in which to generate the target(s) -def makeGenOpts(extensions = [], protect = True, directory = '.'): - global genOpts - genOpts = {} - - # Descriptive names for various regexp patterns used to select - # versions and extensions - allVersions = allExtensions = '.*' - noVersions = noExtensions = None - - addExtensions = makeREstring(extensions) - removeExtensions = makeREstring([]) - - # Copyright text prefixing all headers (list of strings). - prefixStrings = [ - '/*', - '** Copyright (c) 2015-2016 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.', - '*/', - '' - ] - - # Text specific to Vulkan headers - vkPrefixStrings = [ - '/*', - '** This header is generated from the Khronos Vulkan XML API Registry.', - '**', - '*/', - '' - ] - - # Defaults for generating re-inclusion protection wrappers (or not) - protectFile = protect - protectFeature = protect - protectProto = protect - - # - # LoaderAndValidationLayer Generators - # Options for threading layer - genOpts['thread_check.h'] = [ - ThreadOutputGenerator, - ThreadGeneratorOptions( - filename = 'thread_check.h', - directory = directory, - apiname = 'vulkan', - profile = None, - versions = allVersions, - emitversions = allVersions, - defaultExtensions = 'vulkan', - addExtensions = addExtensions, - removeExtensions = removeExtensions, - prefixText = prefixStrings + vkPrefixStrings, - protectFeature = False, - apicall = 'VKAPI_ATTR ', - apientry = 'VKAPI_CALL ', - apientryp = 'VKAPI_PTR *', - alignFuncParam = 48) - ] - - # Options for parameter validation layer - genOpts['parameter_validation.h'] = [ - ParamCheckerOutputGenerator, - ParamCheckerGeneratorOptions( - filename = 'parameter_validation.h', - directory = directory, - apiname = 'vulkan', - profile = None, - versions = allVersions, - emitversions = allVersions, - defaultExtensions = 'vulkan', - addExtensions = addExtensions, - removeExtensions = removeExtensions, - prefixText = prefixStrings + vkPrefixStrings, - protectFeature = False, - apicall = 'VKAPI_ATTR ', - apientry = 'VKAPI_CALL ', - apientryp = 'VKAPI_PTR *', - alignFuncParam = 48) - ] - - # Options for unique objects layer - genOpts['unique_objects_wrappers.h'] = [ - UniqueObjectsOutputGenerator, - UniqueObjectsGeneratorOptions( - filename = 'unique_objects_wrappers.h', - directory = directory, - apiname = 'vulkan', - profile = None, - versions = allVersions, - emitversions = allVersions, - defaultExtensions = 'vulkan', - addExtensions = addExtensions, - removeExtensions = removeExtensions, - prefixText = prefixStrings + vkPrefixStrings, - protectFeature = False, - apicall = 'VKAPI_ATTR ', - apientry = 'VKAPI_CALL ', - apientryp = 'VKAPI_PTR *', - alignFuncParam = 48) - ] - -# Generate a target based on the options in the matching genOpts{} object. -# This is encapsulated in a function so it can be profiled and/or timed. -# The args parameter is an parsed argument object containing the following -# fields that are used: -# target - target to generate -# directory - directory to generate it in -# protect - True if re-inclusion wrappers should be created -# extensions - list of additional extensions to include in generated -# interfaces -def genTarget(args): - global genOpts - - # Create generator options with specified parameters - makeGenOpts(extensions = args.extension, - protect = args.protect, - directory = args.directory) - - if (args.target in genOpts.keys()): - createGenerator = genOpts[args.target][0] - options = genOpts[args.target][1] - - write('* Building', options.filename, file=sys.stderr) - - startTimer(args.time) - gen = createGenerator(errFile=errWarn, - warnFile=errWarn, - diagFile=diag) - reg.setGenerator(gen) - reg.apiGen(options) - write('* Generated', options.filename, file=sys.stderr) - endTimer(args.time, '* Time to generate ' + options.filename + ' =') - else: - write('No generator options for unknown target:', - args.target, file=sys.stderr) - -# -extension name - may be a single extension name, a a space-separated list -# of names, or a regular expression. -if __name__ == '__main__': - parser = argparse.ArgumentParser() - - parser.add_argument('-extension', action='append', - default=[], - help='Specify an extension or extensions to add to targets') - parser.add_argument('-debug', action='store_true', - help='Enable debugging') - parser.add_argument('-dump', action='store_true', - help='Enable dump to stderr') - parser.add_argument('-diagfile', action='store', - default=None, - help='Write diagnostics to specified file') - parser.add_argument('-errfile', action='store', - default=None, - help='Write errors and warnings to specified file instead of stderr') - parser.add_argument('-noprotect', dest='protect', action='store_false', - help='Disable inclusion protection in output headers') - parser.add_argument('-profile', action='store_true', - help='Enable profiling') - parser.add_argument('-registry', action='store', - default='vk.xml', - help='Use specified registry file instead of vk.xml') - parser.add_argument('-time', action='store_true', - help='Enable timing') - parser.add_argument('-validate', action='store_true', - help='Enable group validation') - parser.add_argument('-o', action='store', dest='directory', - default='.', - help='Create target and related files in specified directory') - parser.add_argument('target', metavar='target', nargs='?', - help='Specify target') - - args = parser.parse_args() - - # This splits arguments which are space-separated lists - args.extension = [name for arg in args.extension for name in arg.split()] - - # Load & parse registry - reg = Registry() - - startTimer(args.time) - tree = etree.parse(args.registry) - endTimer(args.time, '* Time to make ElementTree =') - - startTimer(args.time) - reg.loadElementTree(tree) - endTimer(args.time, '* Time to parse ElementTree =') - - if (args.validate): - reg.validateGroups() - - if (args.dump): - write('* Dumping registry to regdump.txt', file=sys.stderr) - reg.dumpReg(filehandle = open('regdump.txt','w')) - - # create error/warning & diagnostic files - if (args.errfile): - errWarn = open(args.errfile, 'w') - else: - errWarn = sys.stderr - - if (args.diagfile): - diag = open(args.diagfile, 'w') - else: - diag = None - - if (args.debug): - pdb.run('genTarget(args)') - elif (args.profile): - import cProfile, pstats - cProfile.run('genTarget(args)', 'profile.txt') - p = pstats.Stats('profile.txt') - p.strip_dirs().sort_stats('time').print_stats(50) - else: - genTarget(args) diff --git a/parameter_validation_generator.py b/parameter_validation_generator.py deleted file mode 100644 index e0d215da..00000000 --- a/parameter_validation_generator.py +++ /dev/null @@ -1,987 +0,0 @@ -#!/usr/bin/python3 -i -# -# Copyright (c) 2015-2016 The Khronos Group Inc. -# Copyright (c) 2015-2016 Valve Corporation -# Copyright (c) 2015-2016 LunarG, Inc. -# Copyright (c) 2015-2016 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: Dustin Graves - -import os,re,sys -import xml.etree.ElementTree as etree -from generator import * -from collections import namedtuple - - -# ParamCheckerGeneratorOptions - subclass of GeneratorOptions. -# -# Adds options used by ParamCheckerOutputGenerator object during Parameter -# validation layer generation. -# -# Additional members -# prefixText - list of strings to prefix generated header with -# (usually a copyright statement + calling convention macros). -# protectFile - True if multiple inclusion protection should be -# generated (based on the filename) around the entire header. -# protectFeature - True if #ifndef..#endif protection should be -# generated around a feature interface in the header file. -# genFuncPointers - True if function pointer typedefs should be -# generated -# protectProto - If conditional protection should be generated -# around prototype declarations, set to either '#ifdef' -# to require opt-in (#ifdef protectProtoStr) or '#ifndef' -# to require opt-out (#ifndef protectProtoStr). Otherwise -# set to None. -# protectProtoStr - #ifdef/#ifndef symbol to use around prototype -# declarations, if protectProto is set -# apicall - string to use for the function declaration prefix, -# such as APICALL on Windows. -# apientry - string to use for the calling convention macro, -# in typedefs, such as APIENTRY. -# apientryp - string to use for the calling convention macro -# in function pointer typedefs, such as APIENTRYP. -# indentFuncProto - True if prototype declarations should put each -# parameter on a separate line -# indentFuncPointer - True if typedefed function pointers should put each -# parameter on a separate line -# alignFuncParam - if nonzero and parameters are being put on a -# separate line, align parameter names at the specified column -class ParamCheckerGeneratorOptions(GeneratorOptions): - def __init__(self, - filename = None, - directory = '.', - apiname = None, - profile = None, - versions = '.*', - emitversions = '.*', - defaultExtensions = None, - addExtensions = None, - removeExtensions = None, - sortProcedure = regSortFeatures, - prefixText = "", - genFuncPointers = True, - protectFile = True, - protectFeature = True, - protectProto = None, - protectProtoStr = None, - apicall = '', - apientry = '', - apientryp = '', - indentFuncProto = True, - indentFuncPointer = False, - alignFuncParam = 0): - GeneratorOptions.__init__(self, filename, directory, apiname, profile, - versions, emitversions, defaultExtensions, - addExtensions, removeExtensions, sortProcedure) - self.prefixText = prefixText - self.genFuncPointers = genFuncPointers - self.protectFile = protectFile - self.protectFeature = protectFeature - self.protectProto = protectProto - self.protectProtoStr = protectProtoStr - self.apicall = apicall - self.apientry = apientry - self.apientryp = apientryp - self.indentFuncProto = indentFuncProto - self.indentFuncPointer = indentFuncPointer - self.alignFuncParam = alignFuncParam - -# ParamCheckerOutputGenerator - subclass of OutputGenerator. -# Generates param checker layer code. -# -# ---- methods ---- -# ParamCheckerOutputGenerator(errFile, warnFile, diagFile) - args as for -# OutputGenerator. Defines additional internal state. -# ---- methods overriding base class ---- -# beginFile(genOpts) -# endFile() -# beginFeature(interface, emit) -# endFeature() -# genType(typeinfo,name) -# genStruct(typeinfo,name) -# genGroup(groupinfo,name) -# genEnum(enuminfo, name) -# genCmd(cmdinfo) -class ParamCheckerOutputGenerator(OutputGenerator): - """Generate ParamChecker code based on XML element attributes""" - # This is an ordered list of sections in the header file. - ALL_SECTIONS = ['command'] - def __init__(self, - errFile = sys.stderr, - warnFile = sys.stderr, - diagFile = sys.stdout): - OutputGenerator.__init__(self, errFile, warnFile, diagFile) - self.INDENT_SPACES = 4 - # Commands to ignore - self.blacklist = [ - 'vkGetInstanceProcAddr', - 'vkGetDeviceProcAddr', - 'vkEnumerateInstanceLayerProperties', - 'vkEnumerateInstanceExtensionsProperties', - 'vkEnumerateDeviceLayerProperties', - 'vkEnumerateDeviceExtensionsProperties', - 'vkCreateDebugReportCallbackEXT', - 'vkDebugReportMessageEXT'] - # Validation conditions for some special case struct members that are conditionally validated - self.structMemberValidationConditions = { 'VkPipelineColorBlendStateCreateInfo' : { 'logicOp' : '{}logicOpEnable == VK_TRUE' } } - # Header version - self.headerVersion = None - # Internal state - accumulators for different inner block text - self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) - self.structNames = [] # List of Vulkan struct typenames - self.stypes = [] # Values from the VkStructureType enumeration - self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType - self.handleTypes = set() # Set of handle type names - self.commands = [] # List of CommandData records for all Vulkan commands - self.structMembers = [] # List of StructMemberData records for all Vulkan structs - self.validatedStructs = dict() # Map of structs type names to generated validation code for that struct type - self.enumRanges = dict() # Map of enum name to BEGIN/END range values - self.flags = set() # Map of flags typenames - self.flagBits = dict() # Map of flag bits typename to list of values - # Named tuples to store struct and command data - self.StructType = namedtuple('StructType', ['name', 'value']) - self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isstaticarray', 'isbool', 'israngedenum', - 'isconst', 'isoptional', 'iscount', 'noautovalidity', 'len', 'extstructs', - 'condition', 'cdecl']) - self.CommandData = namedtuple('CommandData', ['name', 'params', 'cdecl']) - self.StructMemberData = namedtuple('StructMemberData', ['name', 'members']) - # - def incIndent(self, indent): - inc = ' ' * self.INDENT_SPACES - if indent: - return indent + inc - return inc - # - def decIndent(self, indent): - if indent and (len(indent) > self.INDENT_SPACES): - return indent[:-self.INDENT_SPACES] - return '' - # - def beginFile(self, genOpts): - OutputGenerator.beginFile(self, genOpts) - # C-specific - # - # User-supplied prefix text, if any (list of strings) - if (genOpts.prefixText): - for s in genOpts.prefixText: - write(s, file=self.outFile) - # - # Multiple inclusion protection & C++ wrappers. - if (genOpts.protectFile and self.genOpts.filename): - headerSym = re.sub('\.h', '_H', os.path.basename(self.genOpts.filename)).upper() - write('#ifndef', headerSym, file=self.outFile) - write('#define', headerSym, '1', file=self.outFile) - self.newline() - # - # Headers - write('#include ', file=self.outFile) - self.newline() - write('#include "vulkan/vulkan.h"', file=self.outFile) - write('#include "vk_layer_extension_utils.h"', file=self.outFile) - write('#include "parameter_validation_utils.h"', file=self.outFile) - # - # Macros - self.newline() - write('#ifndef UNUSED_PARAMETER', file=self.outFile) - write('#define UNUSED_PARAMETER(x) (void)(x)', file=self.outFile) - write('#endif // UNUSED_PARAMETER', file=self.outFile) - # - # Namespace - self.newline() - write('namespace parameter_validation {', file = self.outFile) - def endFile(self): - # C-specific - self.newline() - # Namespace - write('} // namespace parameter_validation', file = self.outFile) - # Finish C++ wrapper and multiple inclusion protection - if (self.genOpts.protectFile and self.genOpts.filename): - self.newline() - write('#endif', file=self.outFile) - # Finish processing in superclass - OutputGenerator.endFile(self) - def beginFeature(self, interface, emit): - # Start processing in superclass - OutputGenerator.beginFeature(self, interface, emit) - # C-specific - # Accumulate includes, defines, types, enums, function pointer typedefs, - # end function prototypes separately for this feature. They're only - # printed in endFeature(). - self.headerVersion = None - self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) - self.structNames = [] - self.stypes = [] - self.structTypes = dict() - self.handleTypes = set() - self.commands = [] - self.structMembers = [] - self.validatedStructs = dict() - self.enumRanges = dict() - self.flags = set() - self.flagBits = dict() - def endFeature(self): - # C-specific - # Actually write the interface to the output file. - if (self.emit): - self.newline() - # If type declarations are needed by other features based on - # this one, it may be necessary to suppress the ExtraProtect, - # or move it below the 'for section...' loop. - if (self.featureExtraProtect != None): - write('#ifdef', self.featureExtraProtect, file=self.outFile) - # Generate the struct member checking code from the captured data - self.processStructMemberData() - # Generate the command parameter checking code from the captured data - self.processCmdData() - # Write the declaration for the HeaderVersion - if self.headerVersion: - write('const uint32_t GeneratedHeaderVersion = {};'.format(self.headerVersion), file=self.outFile) - self.newline() - # Write the declarations for the VkFlags values combining all flag bits - for flag in sorted(self.flags): - flagBits = flag.replace('Flags', 'FlagBits') - if flagBits in self.flagBits: - bits = self.flagBits[flagBits] - decl = 'const {} All{} = {}'.format(flag, flagBits, bits[0]) - for bit in bits[1:]: - decl += '|' + bit - decl += ';' - write(decl, file=self.outFile) - self.newline() - # Write the parameter validation code to the file - if (self.sections['command']): - if (self.genOpts.protectProto): - write(self.genOpts.protectProto, - self.genOpts.protectProtoStr, file=self.outFile) - write('\n'.join(self.sections['command']), end='', file=self.outFile) - if (self.featureExtraProtect != None): - write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) - else: - self.newline() - # Finish processing in superclass - OutputGenerator.endFeature(self) - # - # Append a definition to the specified section - def appendSection(self, section, text): - # self.sections[section].append('SECTION: ' + section + '\n') - self.sections[section].append(text) - # - # Type generation - def genType(self, typeinfo, name): - OutputGenerator.genType(self, typeinfo, name) - typeElem = typeinfo.elem - # If the type is a struct type, traverse the imbedded tags - # generating a structure. Otherwise, emit the tag text. - category = typeElem.get('category') - if (category == 'struct' or category == 'union'): - self.structNames.append(name) - self.genStruct(typeinfo, name) - elif (category == 'handle'): - self.handleTypes.add(name) - elif (category == 'bitmask'): - self.flags.add(name) - elif (category == 'define'): - if name == 'VK_HEADER_VERSION': - nameElem = typeElem.find('name') - self.headerVersion = noneStr(nameElem.tail).strip() - # - # Struct parameter check generation. - # This is a special case of the tag where the contents are - # interpreted as a set of tags instead of freeform C - # C type declarations. The tags are just like - # tags - they are a declaration of a struct or union member. - # Only simple member declarations are supported (no nested - # structs etc.) - def genStruct(self, typeinfo, typeName): - OutputGenerator.genStruct(self, typeinfo, typeName) - conditions = self.structMemberValidationConditions[typeName] if typeName in self.structMemberValidationConditions else None - 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] - stypeValue = '' - cdecl = self.makeCParamDecl(member, 0) - # 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 - # Check for parameter name in lens set - iscount = False - if name in lens: - iscount = True - # The pNext members are not tagged as optional, but are treated as - # optional for parameter NULL checks. Static array members - # are also treated as optional to skip NULL pointer validation, as - # they won't be NULL. - isstaticarray = self.paramIsStaticArray(member) - isoptional = False - if self.paramIsOptional(member) or (name == 'pNext') or (isstaticarray): - isoptional = True - membersInfo.append(self.CommandParam(type=type, name=name, - ispointer=self.paramIsPointer(member), - isstaticarray=isstaticarray, - isbool=True if type == 'VkBool32' else False, - israngedenum=True if type in self.enumRanges else False, - isconst=True if 'const' in cdecl else False, - isoptional=isoptional, - iscount=iscount, - noautovalidity=True if member.attrib.get('noautovalidity') is not None else False, - len=self.getLen(member), - extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None, - condition=conditions[name] if conditions and name in conditions else None, - cdecl=cdecl)) - self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo)) - # - # Capture group (e.g. C "enum" type) info to be used for - # param check code generation. - # These are concatenated together with other types. - def genGroup(self, groupinfo, groupName): - OutputGenerator.genGroup(self, groupinfo, groupName) - groupElem = groupinfo.elem - # - # Store the sType values - if groupName == 'VkStructureType': - for elem in groupElem.findall('enum'): - self.stypes.append(elem.get('name')) - elif 'FlagBits' in groupName: - bits = [] - for elem in groupElem.findall('enum'): - bits.append(elem.get('name')) - if bits: - self.flagBits[groupName] = bits - else: - # Determine if begin/end ranges are needed (we don't do this for VkStructureType, which has a more finely grained check) - expandName = re.sub(r'([0-9a-z_])([A-Z0-9][^A-Z0-9]?)',r'\1_\2',groupName).upper() - expandPrefix = expandName - expandSuffix = '' - expandSuffixMatch = re.search(r'[A-Z][A-Z]+$',groupName) - if expandSuffixMatch: - expandSuffix = '_' + expandSuffixMatch.group() - # Strip off the suffix from the prefix - expandPrefix = expandName.rsplit(expandSuffix, 1)[0] - isEnum = ('FLAG_BITS' not in expandPrefix) - if isEnum: - self.enumRanges[groupName] = (expandPrefix + '_BEGIN_RANGE' + expandSuffix, expandPrefix + '_END_RANGE' + expandSuffix) - # - # Capture command parameter info to be used for param - # check code generation. - def genCmd(self, cmdinfo, name): - OutputGenerator.genCmd(self, cmdinfo, name) - if name not in self.blacklist: - params = cmdinfo.elem.findall('param') - # Get list of array lengths - lens = set() - for param in params: - len = self.getLen(param) - if len: - lens.add(len) - # Get param info - paramsInfo = [] - for param in params: - paramInfo = self.getTypeNameTuple(param) - cdecl = self.makeCParamDecl(param, 0) - # Check for parameter name in lens set - iscount = False - if paramInfo[1] in lens: - iscount = True - paramsInfo.append(self.CommandParam(type=paramInfo[0], name=paramInfo[1], - ispointer=self.paramIsPointer(param), - isstaticarray=self.paramIsStaticArray(param), - isbool=True if paramInfo[0] == 'VkBool32' else False, - israngedenum=True if paramInfo[0] in self.enumRanges else False, - isconst=True if 'const' in cdecl else False, - isoptional=self.paramIsOptional(param), - iscount=iscount, - noautovalidity=True if param.attrib.get('noautovalidity') is not None else False, - len=self.getLen(param), - extstructs=None, - condition=None, - cdecl=cdecl)) - self.commands.append(self.CommandData(name=name, params=paramsInfo, cdecl=self.makeCDecls(cmdinfo.elem)[0])) - # - # Check if the parameter passed in is a pointer - def paramIsPointer(self, param): - ispointer = 0 - paramtype = param.find('type') - if (paramtype.tail is not None) and ('*' in paramtype.tail): - ispointer = paramtype.tail.count('*') - elif paramtype.text[:4] == 'PFN_': - # Treat function pointer typedefs as a pointer to a single value - ispointer = 1 - 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 - # - # Check if the parameter passed in is optional - # Returns a list of Boolean values for comma separated len attributes (len='false,true') - def paramIsOptional(self, param): - # See if the handle is optional - isoptional = False - # Simple, if it's optional, return true - optString = param.attrib.get('optional') - if optString: - if optString == 'true': - isoptional = True - elif ',' in optString: - opts = [] - for opt in optString.split(','): - val = opt.strip() - if val == 'true': - opts.append(True) - elif val == 'false': - opts.append(False) - else: - print('Unrecognized len attribute value',val) - isoptional = opts - return isoptional - # - # Check if the handle passed in is optional - # Uses the same logic as ValidityOutputGenerator.isHandleOptional - def isHandleOptional(self, param, lenParam): - # Simple, if it's optional, return true - if param.isoptional: - return True - # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes. - if param.noautovalidity: - return True - # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional - if lenParam and lenParam.isoptional: - return True - return False - # - # 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) - # - # Get the cached VkStructureType value for the specified struct typename, or generate a VkStructureType - # value assuming the struct is defined by a different feature - def getStructType(self, typename): - value = None - if typename in self.structTypes: - value = self.structTypes[typename].value - else: - value = self.genVkStructureType(typename) - self.logMsg('diag', 'ParameterValidation: Generating {} for {} structure type that was not defined by the current feature'.format(value, typename)) - return value - # - # 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 - result = str(result).replace('::', '->') - return result - # - # 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) - # - # Find a named parameter in a parameter list - def getParamByName(self, params, name): - for param in params: - if param.name == name: - return param - return None - # - # Extract length values from latexmath. Currently an inflexible solution that looks for specific - # patterns that are found in vk.xml. Will need to be updated when new patterns are introduced. - def parseLateXMath(self, source): - name = 'ERROR' - decoratedName = 'ERROR' - if 'mathit' in source: - # Matches expressions similar to 'latexmath:[$\lceil{\mathit{rasterizationSamples} \over 32}\rceil$]' - match = re.match(r'latexmath\s*\:\s*\[\s*\$\\l(\w+)\s*\{\s*\\mathit\s*\{\s*(\w+)\s*\}\s*\\over\s*(\d+)\s*\}\s*\\r(\w+)\$\s*\]', source) - if not match or match.group(1) != match.group(4): - raise 'Unrecognized latexmath expression' - name = match.group(2) - decoratedName = '{}({}/{})'.format(*match.group(1, 2, 3)) - else: - # Matches expressions similar to 'latexmath : [$dataSize \over 4$]' - match = re.match(r'latexmath\s*\:\s*\[\s*\$\s*(\w+)\s*\\over\s*(\d+)\s*\$\s*\]', source) - name = match.group(1) - decoratedName = '{}/{}'.format(*match.group(1, 2)) - return name, decoratedName - # - # Get the length paramater record for the specified parameter name - def getLenParam(self, params, name): - lenParam = None - if name: - if '->' in name: - # The count is obtained by dereferencing a member of a struct parameter - lenParam = self.CommandParam(name=name, iscount=True, ispointer=False, isbool=False, israngedenum=False, isconst=False, - isstaticarray=None, isoptional=False, type=None, noautovalidity=False, len=None, extstructs=None, - condition=None, cdecl=None) - elif 'latexmath' in name: - lenName, decoratedName = self.parseLateXMath(name) - lenParam = self.getParamByName(params, lenName) - # TODO: Zero-check the result produced by the equation? - # Copy the stored len parameter entry and overwrite the name with the processed latexmath equation - #param = self.getParamByName(params, lenName) - #lenParam = self.CommandParam(name=decoratedName, iscount=param.iscount, ispointer=param.ispointer, - # isoptional=param.isoptional, type=param.type, len=param.len, - # isstaticarray=param.isstaticarray, extstructs=param.extstructs, - # noautovalidity=True, condition=None, cdecl=param.cdecl) - else: - lenParam = self.getParamByName(params, name) - return lenParam - # - # Convert a vulkan.h command declaration into a parameter_validation.h definition - def getCmdDef(self, cmd): - # - # Strip the trailing ';' and split into individual lines - lines = cmd.cdecl[:-1].split('\n') - # Replace Vulkan prototype - lines[0] = 'static bool parameter_validation_' + cmd.name + '(' - # Replace the first argument with debug_report_data, when the first - # argument is a handle (not vkCreateInstance) - reportData = ' debug_report_data*'.ljust(self.genOpts.alignFuncParam) + 'report_data,' - if cmd.name != 'vkCreateInstance': - lines[1] = reportData - else: - lines.insert(1, reportData) - return '\n'.join(lines) - # - # Generate the code to check for a NULL dereference before calling the - # validation function - def genCheckedLengthCall(self, name, exprs): - count = name.count('->') - if count: - checkedExpr = [] - localIndent = '' - elements = name.split('->') - # Open the if expression blocks - for i in range(0, count): - checkedExpr.append(localIndent + 'if ({} != NULL) {{\n'.format('->'.join(elements[0:i+1]))) - localIndent = self.incIndent(localIndent) - # Add the validation expression - for expr in exprs: - checkedExpr.append(localIndent + expr) - # Close the if blocks - for i in range(0, count): - localIndent = self.decIndent(localIndent) - checkedExpr.append(localIndent + '}\n') - return [checkedExpr] - # No if statements were required - return exprs - # - # Generate code to check for a specific condition before executing validation code - def genConditionalCall(self, prefix, condition, exprs): - checkedExpr = [] - localIndent = '' - formattedCondition = condition.format(prefix) - checkedExpr.append(localIndent + 'if ({})\n'.format(formattedCondition)) - checkedExpr.append(localIndent + '{\n') - localIndent = self.incIndent(localIndent) - for expr in exprs: - checkedExpr.append(localIndent + expr) - localIndent = self.decIndent(localIndent) - checkedExpr.append(localIndent + '}\n') - return [checkedExpr] - # - # Generate the sType check string - def makeStructTypeCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, lenPtrRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec): - checkExpr = [] - stype = self.structTypes[value.type] - if lenValue: - # This is an array with a pointer to a count value - if lenValue.ispointer: - # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required - checkExpr.append('skipCall |= validate_struct_type_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {}, {});\n'.format( - funcPrintName, lenPtrRequired, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, sv=stype.value, pf=prefix, **postProcSpec)) - # This is an array with an integer count value - else: - checkExpr.append('skipCall |= validate_struct_type_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {});\n'.format( - funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, sv=stype.value, pf=prefix, **postProcSpec)) - # This is an individual struct - else: - checkExpr.append('skipCall |= validate_struct_type(report_data, "{}", {ppp}"{}"{pps}, "{sv}", {}{vn}, {sv}, {});\n'.format( - funcPrintName, valuePrintName, prefix, valueRequired, vn=value.name, sv=stype.value, **postProcSpec)) - return checkExpr - # - # Generate the handle check string - def makeHandleCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec): - checkExpr = [] - if lenValue: - if lenValue.ispointer: - # This is assumed to be an output array with a pointer to a count value - raise('Unsupported parameter validation case: Output handle array elements are not NULL checked') - else: - # This is an array with an integer count value - checkExpr.append('skipCall |= validate_handle_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format( - funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec)) - else: - # This is assumed to be an output handle pointer - raise('Unsupported parameter validation case: Output handles are not NULL checked') - return checkExpr - # - # Generate check string for an array of VkFlags values - def makeFlagsArrayCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec): - checkExpr = [] - flagBitsName = value.type.replace('Flags', 'FlagBits') - if not flagBitsName in self.flagBits: - raise('Unsupported parameter validation case: array of reserved VkFlags') - else: - allFlags = 'All' + flagBitsName - checkExpr.append('skipCall |= validate_flags_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, "{}", {}, {pf}{}, {pf}{}, {}, {});\n'.format(funcPrintName, lenPrintName, valuePrintName, flagBitsName, allFlags, lenValue.name, value.name, lenValueRequired, valueRequired, pf=prefix, **postProcSpec)) - return checkExpr - # - # Generate pNext check string - def makeStructNextCheck(self, prefix, value, funcPrintName, valuePrintName, postProcSpec): - checkExpr = [] - # Generate an array of acceptable VkStructureType values for pNext - extStructCount = 0 - extStructVar = 'NULL' - extStructNames = 'NULL' - if value.extstructs: - structs = value.extstructs.split(',') - checkExpr.append('const VkStructureType allowedStructs[] = {' + ', '.join([self.getStructType(s) for s in structs]) + '};\n') - extStructCount = 'ARRAY_SIZE(allowedStructs)' - extStructVar = 'allowedStructs' - extStructNames = '"' + ', '.join(structs) + '"' - checkExpr.append('skipCall |= validate_struct_pnext(report_data, "{}", {ppp}"{}"{pps}, {}, {}{}, {}, {}, GeneratedHeaderVersion);\n'.format( - funcPrintName, valuePrintName, extStructNames, prefix, value.name, extStructCount, extStructVar, **postProcSpec)) - return checkExpr - # - # Generate the pointer check string - def makePointerCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, lenPtrRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec): - checkExpr = [] - if lenValue: - # This is an array with a pointer to a count value - if lenValue.ispointer: - # If count and array parameters are optional, there will be no validation - if valueRequired == 'true' or lenPtrRequired == 'true' or lenValueRequired == 'true': - # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required - checkExpr.append('skipCall |= validate_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {}, {});\n'.format( - funcPrintName, lenPtrRequired, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec)) - # This is an array with an integer count value - else: - # If count and array parameters are optional, there will be no validation - if valueRequired == 'true' or lenValueRequired == 'true': - # Arrays of strings receive special processing - validationFuncName = 'validate_array' if value.type != 'char' else 'validate_string_array' - checkExpr.append('skipCall |= {}(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format( - validationFuncName, funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec)) - if checkExpr: - if lenValue and ('->' in lenValue.name): - # Add checks to ensure the validation call does not dereference a NULL pointer to obtain the count - checkExpr = self.genCheckedLengthCall(lenValue.name, checkExpr) - # This is an individual struct that is not allowed to be NULL - elif not value.isoptional: - # Function pointers need a reinterpret_cast to void* - if value.type[:4] == 'PFN_': - checkExpr.append('skipCall |= validate_required_pointer(report_data, "{}", {ppp}"{}"{pps}, reinterpret_cast({}{}));\n'.format(funcPrintName, valuePrintName, prefix, value.name, **postProcSpec)) - else: - checkExpr.append('skipCall |= validate_required_pointer(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcPrintName, valuePrintName, prefix, value.name, **postProcSpec)) - return checkExpr - # - # Process struct member validation code, performing name suibstitution if required - def processStructMemberCode(self, line, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec): - # Build format specifier list - kwargs = {} - if '{postProcPrefix}' in line: - # If we have a tuple that includes a format string and format parameters, need to use ParameterName class - if type(memberDisplayNamePrefix) is tuple: - kwargs['postProcPrefix'] = 'ParameterName(' - else: - kwargs['postProcPrefix'] = postProcSpec['ppp'] - if '{postProcSuffix}' in line: - # If we have a tuple that includes a format string and format parameters, need to use ParameterName class - if type(memberDisplayNamePrefix) is tuple: - kwargs['postProcSuffix'] = ', ParameterName::IndexVector{{ {}{} }})'.format(postProcSpec['ppi'], memberDisplayNamePrefix[1]) - else: - kwargs['postProcSuffix'] = postProcSpec['pps'] - if '{postProcInsert}' in line: - # If we have a tuple that includes a format string and format parameters, need to use ParameterName class - if type(memberDisplayNamePrefix) is tuple: - kwargs['postProcInsert'] = '{}{}, '.format(postProcSpec['ppi'], memberDisplayNamePrefix[1]) - else: - kwargs['postProcInsert'] = postProcSpec['ppi'] - if '{funcName}' in line: - kwargs['funcName'] = funcName - if '{valuePrefix}' in line: - kwargs['valuePrefix'] = memberNamePrefix - if '{displayNamePrefix}' in line: - # Check for a tuple that includes a format string and format parameters to be used with the ParameterName class - if type(memberDisplayNamePrefix) is tuple: - kwargs['displayNamePrefix'] = memberDisplayNamePrefix[0] - else: - kwargs['displayNamePrefix'] = memberDisplayNamePrefix - - if kwargs: - # Need to escape the C++ curly braces - if 'IndexVector' in line: - line = line.replace('IndexVector{ ', 'IndexVector{{ ') - line = line.replace(' }),', ' }}),') - return line.format(**kwargs) - return line - # - # Process struct validation code for inclusion in function or parent struct validation code - def expandStructCode(self, lines, funcName, memberNamePrefix, memberDisplayNamePrefix, indent, output, postProcSpec): - for line in lines: - if output: - output[-1] += '\n' - if type(line) is list: - for sub in line: - output.append(self.processStructMemberCode(indent + sub, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec)) - else: - output.append(self.processStructMemberCode(indent + line, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec)) - return output - # - # Process struct pointer/array validation code, perfoeming name substitution if required - def expandStructPointerCode(self, prefix, value, lenValue, funcName, valueDisplayName, postProcSpec): - expr = [] - expr.append('if ({}{} != NULL)\n'.format(prefix, value.name)) - expr.append('{') - indent = self.incIndent(None) - if lenValue: - # Need to process all elements in the array - indexName = lenValue.name.replace('Count', 'Index') - expr[-1] += '\n' - expr.append(indent + 'for (uint32_t {iname} = 0; {iname} < {}{}; ++{iname})\n'.format(prefix, lenValue.name, iname=indexName)) - expr.append(indent + '{') - indent = self.incIndent(indent) - # Prefix for value name to display in error message - memberNamePrefix = '{}{}[{}].'.format(prefix, value.name, indexName) - memberDisplayNamePrefix = ('{}[%i].'.format(valueDisplayName), indexName) - else: - memberNamePrefix = '{}{}->'.format(prefix, value.name) - memberDisplayNamePrefix = '{}->'.format(valueDisplayName) - # - # Expand the struct validation lines - expr = self.expandStructCode(self.validatedStructs[value.type], funcName, memberNamePrefix, memberDisplayNamePrefix, indent, expr, postProcSpec) - # - if lenValue: - # Close if and for scopes - indent = self.decIndent(indent) - expr.append(indent + '}\n') - expr.append('}\n') - return expr - # - # Generate the parameter checking code - def genFuncBody(self, funcName, values, valuePrefix, displayNamePrefix, structTypeName): - lines = [] # Generated lines of code - unused = [] # Unused variable names - for value in values: - usedLines = [] - lenParam = None - # - # Prefix and suffix for post processing of parameter names for struct members. Arrays of structures need special processing to include the array index in the full parameter name. - postProcSpec = {} - postProcSpec['ppp'] = '' if not structTypeName else '{postProcPrefix}' - postProcSpec['pps'] = '' if not structTypeName else '{postProcSuffix}' - postProcSpec['ppi'] = '' if not structTypeName else '{postProcInsert}' - # - # Generate the full name of the value, which will be printed in the error message, by adding the variable prefix to the value name - valueDisplayName = '{}{}'.format(displayNamePrefix, value.name) - # - # Check for NULL pointers, ignore the inout count parameters that - # will be validated with their associated array - if (value.ispointer or value.isstaticarray) and not value.iscount: - # - # Parameters for function argument generation - req = 'true' # Paramerter cannot be NULL - cpReq = 'true' # Count pointer cannot be NULL - cvReq = 'true' # Count value cannot be 0 - lenDisplayName = None # Name of length parameter to print with validation messages; parameter name with prefix applied - # - # Generate required/optional parameter strings for the pointer and count values - if value.isoptional: - req = 'false' - if value.len: - # The parameter is an array with an explicit count parameter - lenParam = self.getLenParam(values, value.len) - lenDisplayName = '{}{}'.format(displayNamePrefix, lenParam.name) - if lenParam.ispointer: - # Count parameters that are pointers are inout - if type(lenParam.isoptional) is list: - if lenParam.isoptional[0]: - cpReq = 'false' - if lenParam.isoptional[1]: - cvReq = 'false' - else: - if lenParam.isoptional: - cpReq = 'false' - else: - if lenParam.isoptional: - cvReq = 'false' - # - # The parameter will not be processes when tagged as 'noautovalidity' - # For the pointer to struct case, the struct pointer will not be validated, but any - # members not tagged as 'noatuvalidity' will be validated - if value.noautovalidity: - # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually - self.logMsg('diag', 'ParameterValidation: No validation for {} {}'.format(structTypeName if structTypeName else funcName, value.name)) - else: - # - # If this is a pointer to a struct with an sType field, verify the type - if value.type in self.structTypes: - usedLines += self.makeStructTypeCheck(valuePrefix, value, lenParam, req, cvReq, cpReq, funcName, lenDisplayName, valueDisplayName, postProcSpec) - # If this is an input handle array that is not allowed to contain NULL handles, verify that none of the handles are VK_NULL_HANDLE - elif value.type in self.handleTypes and value.isconst and not self.isHandleOptional(value, lenParam): - usedLines += self.makeHandleCheck(valuePrefix, value, lenParam, req, cvReq, funcName, lenDisplayName, valueDisplayName, postProcSpec) - elif value.type in self.flags and value.isconst: - usedLines += self.makeFlagsArrayCheck(valuePrefix, value, lenParam, req, cvReq, funcName, lenDisplayName, valueDisplayName, postProcSpec) - elif value.isbool and value.isconst: - usedLines.append('skipCall |= validate_bool32_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, {pf}{}, {pf}{}, {}, {});\n'.format(funcName, lenDisplayName, valueDisplayName, lenParam.name, value.name, cvReq, req, pf=valuePrefix, **postProcSpec)) - elif value.israngedenum and value.isconst: - enumRange = self.enumRanges[value.type] - usedLines.append('skipCall |= validate_ranged_enum_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, "{}", {}, {}, {pf}{}, {pf}{}, {}, {});\n'.format(funcName, lenDisplayName, valueDisplayName, value.type, enumRange[0], enumRange[1], lenParam.name, value.name, cvReq, req, pf=valuePrefix, **postProcSpec)) - elif value.name == 'pNext': - # We need to ignore VkDeviceCreateInfo and VkInstanceCreateInfo, as the loader manipulates them in a way that is not documented in vk.xml - if not structTypeName in ['VkDeviceCreateInfo', 'VkInstanceCreateInfo']: - usedLines += self.makeStructNextCheck(valuePrefix, value, funcName, valueDisplayName, postProcSpec) - else: - usedLines += self.makePointerCheck(valuePrefix, value, lenParam, req, cvReq, cpReq, funcName, lenDisplayName, valueDisplayName, postProcSpec) - # - # If this is a pointer to a struct (input), see if it contains members that need to be checked - if value.type in self.validatedStructs and value.isconst: - usedLines.append(self.expandStructPointerCode(valuePrefix, value, lenParam, funcName, valueDisplayName, postProcSpec)) - # Non-pointer types - else: - # - # The parameter will not be processes when tagged as 'noautovalidity' - # For the struct case, the struct type will not be validated, but any - # members not tagged as 'noatuvalidity' will be validated - if value.noautovalidity: - # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually - self.logMsg('diag', 'ParameterValidation: No validation for {} {}'.format(structTypeName if structTypeName else funcName, value.name)) - else: - if value.type in self.structTypes: - stype = self.structTypes[value.type] - usedLines.append('skipCall |= validate_struct_type(report_data, "{}", {ppp}"{}"{pps}, "{sv}", &({}{vn}), {sv}, false);\n'.format( - funcName, valueDisplayName, valuePrefix, vn=value.name, sv=stype.value, **postProcSpec)) - elif value.type in self.handleTypes: - if not self.isHandleOptional(value, None): - usedLines.append('skipCall |= validate_required_handle(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcName, valueDisplayName, valuePrefix, value.name, **postProcSpec)) - elif value.type in self.flags: - flagBitsName = value.type.replace('Flags', 'FlagBits') - if not flagBitsName in self.flagBits: - usedLines.append('skipCall |= validate_reserved_flags(report_data, "{}", {ppp}"{}"{pps}, {pf}{});\n'.format(funcName, valueDisplayName, value.name, pf=valuePrefix, **postProcSpec)) - else: - flagsRequired = 'false' if value.isoptional else 'true' - allFlagsName = 'All' + flagBitsName - usedLines.append('skipCall |= validate_flags(report_data, "{}", {ppp}"{}"{pps}, "{}", {}, {pf}{}, {});\n'.format(funcName, valueDisplayName, flagBitsName, allFlagsName, value.name, flagsRequired, pf=valuePrefix, **postProcSpec)) - elif value.isbool: - usedLines.append('skipCall |= validate_bool32(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcName, valueDisplayName, valuePrefix, value.name, **postProcSpec)) - elif value.israngedenum: - enumRange = self.enumRanges[value.type] - usedLines.append('skipCall |= validate_ranged_enum(report_data, "{}", {ppp}"{}"{pps}, "{}", {}, {}, {}{});\n'.format(funcName, valueDisplayName, value.type, enumRange[0], enumRange[1], valuePrefix, value.name, **postProcSpec)) - # - # If this is a struct, see if it contains members that need to be checked - if value.type in self.validatedStructs: - memberNamePrefix = '{}{}.'.format(valuePrefix, value.name) - memberDisplayNamePrefix = '{}.'.format(valueDisplayName) - usedLines.append(self.expandStructCode(self.validatedStructs[value.type], funcName, memberNamePrefix, memberDisplayNamePrefix, '', [], postProcSpec)) - # - # Append the parameter check to the function body for the current command - if usedLines: - # Apply special conditional checks - if value.condition: - usedLines = self.genConditionalCall(valuePrefix, value.condition, usedLines) - lines += usedLines - elif not value.iscount: - # If no expression was generated for this value, it is unreferenced by the validation function, unless - # it is an array count, which is indirectly referenced for array valiadation. - unused.append(value.name) - return lines, unused - # - # Generate the struct member check code from the captured data - def processStructMemberData(self): - indent = self.incIndent(None) - for struct in self.structMembers: - # - # The string returned by genFuncBody will be nested in an if check for a NULL pointer, so needs its indent incremented - lines, unused = self.genFuncBody('{funcName}', struct.members, '{valuePrefix}', '{displayNamePrefix}', struct.name) - if lines: - self.validatedStructs[struct.name] = lines - # - # Generate the command param check code from the captured data - def processCmdData(self): - indent = self.incIndent(None) - for command in self.commands: - # Skip first parameter if it is a dispatch handle (everything except vkCreateInstance) - startIndex = 0 if command.name == 'vkCreateInstance' else 1 - lines, unused = self.genFuncBody(command.name, command.params[startIndex:], '', '', None) - if lines: - cmdDef = self.getCmdDef(command) + '\n' - cmdDef += '{\n' - # Process unused parameters, Ignoring the first dispatch handle parameter, which is not - # processed by parameter_validation (except for vkCreateInstance, which does not have a - # handle as its first parameter) - if unused: - for name in unused: - cmdDef += indent + 'UNUSED_PARAMETER({});\n'.format(name) - if len(unused) > 0: - cmdDef += '\n' - cmdDef += indent + 'bool skipCall = false;\n' - for line in lines: - cmdDef += '\n' - if type(line) is list: - for sub in line: - cmdDef += indent + sub - else: - cmdDef += indent + line - cmdDef += '\n' - cmdDef += indent + 'return skipCall;\n' - cmdDef += '}\n' - self.appendSection('command', cmdDef) diff --git a/reg.py b/reg.py deleted file mode 100755 index 98436a33..00000000 --- a/reg.py +++ /dev/null @@ -1,813 +0,0 @@ -#!/usr/bin/python3 -i -# -# Copyright (c) 2013-2016 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 io,os,re,string,sys,copy -import xml.etree.ElementTree as etree - -# matchAPIProfile - returns whether an API and profile -# being generated matches an element's profile -# api - string naming the API to match -# profile - string naming the profile to match -# elem - Element which (may) have 'api' and 'profile' -# attributes to match to. -# If a tag is not present in the Element, the corresponding API -# or profile always matches. -# Otherwise, the tag must exactly match the API or profile. -# Thus, if 'profile' = core: -# with no attribute will match -# will match -# will not match -# Possible match conditions: -# Requested Element -# Profile Profile -# --------- -------- -# None None Always matches -# 'string' None Always matches -# None 'string' Does not match. Can't generate multiple APIs -# or profiles, so if an API/profile constraint -# is present, it must be asked for explicitly. -# 'string' 'string' Strings must match -# -# ** In the future, we will allow regexes for the attributes, -# not just strings, so that api="^(gl|gles2)" will match. Even -# this isn't really quite enough, we might prefer something -# like "gl(core)|gles1(common-lite)". -def matchAPIProfile(api, profile, elem): - """Match a requested API & profile name to a api & profile attributes of an Element""" - match = True - # Match 'api', if present - if ('api' in elem.attrib): - if (api == None): - raise UserWarning("No API requested, but 'api' attribute is present with value '" + - elem.get('api') + "'") - elif (api != elem.get('api')): - # Requested API doesn't match attribute - return False - if ('profile' in elem.attrib): - if (profile == None): - raise UserWarning("No profile requested, but 'profile' attribute is present with value '" + - elem.get('profile') + "'") - elif (profile != elem.get('profile')): - # Requested profile doesn't match attribute - return False - return True - -# BaseInfo - base class for information about a registry feature -# (type/group/enum/command/API/extension). -# required - should this feature be defined during header generation -# (has it been removed by a profile or version)? -# declared - has this feature been defined already? -# elem - etree Element for this feature -# resetState() - reset required/declared to initial values. Used -# prior to generating a new API interface. -class BaseInfo: - """Represents the state of a registry feature, used during API generation""" - def __init__(self, elem): - self.required = False - self.declared = False - self.elem = elem - def resetState(self): - self.required = False - self.declared = False - -# TypeInfo - registry information about a type. No additional state -# beyond BaseInfo is required. -class TypeInfo(BaseInfo): - """Represents the state of a registry type""" - def __init__(self, elem): - BaseInfo.__init__(self, elem) - self.additionalValidity = [] - self.removedValidity = [] - def resetState(self): - BaseInfo.resetState(self) - self.additionalValidity = [] - self.removedValidity = [] - -# GroupInfo - registry information about a group of related enums -# in an block, generally corresponding to a C "enum" type. -class GroupInfo(BaseInfo): - """Represents the state of a registry group""" - def __init__(self, elem): - BaseInfo.__init__(self, elem) - -# EnumInfo - registry information about an enum -# type - numeric type of the value of the tag -# ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 ) -class EnumInfo(BaseInfo): - """Represents the state of a registry enum""" - def __init__(self, elem): - BaseInfo.__init__(self, elem) - self.type = elem.get('type') - if (self.type == None): - self.type = '' - -# CmdInfo - registry information about a command -class CmdInfo(BaseInfo): - """Represents the state of a registry command""" - def __init__(self, elem): - BaseInfo.__init__(self, elem) - self.additionalValidity = [] - self.removedValidity = [] - def resetState(self): - BaseInfo.resetState(self) - self.additionalValidity = [] - self.removedValidity = [] - -# FeatureInfo - registry information about an API -# or -# name - feature name string (e.g. 'VK_KHR_surface') -# version - feature version number (e.g. 1.2). -# features are unversioned and assigned version number 0. -# ** This is confusingly taken from the 'number' attribute of . -# Needs fixing. -# number - extension number, used for ordering and for -# assigning enumerant offsets. features do -# not have extension numbers and are assigned number 0. -# category - category, e.g. VERSION or khr/vendor tag -# emit - has this feature been defined already? -class FeatureInfo(BaseInfo): - """Represents the state of an API feature (version/extension)""" - def __init__(self, elem): - BaseInfo.__init__(self, elem) - self.name = elem.get('name') - # Determine element category (vendor). Only works - # for elements. - if (elem.tag == 'feature'): - self.category = 'VERSION' - self.version = elem.get('number') - self.number = "0" - self.supported = None - else: - self.category = self.name.split('_', 2)[1] - self.version = "0" - self.number = elem.get('number') - self.supported = elem.get('supported') - self.emit = False - -from generator import write, GeneratorOptions, OutputGenerator - -# Registry - object representing an API registry, loaded from an XML file -# Members -# tree - ElementTree containing the root -# typedict - dictionary of TypeInfo objects keyed by type name -# groupdict - dictionary of GroupInfo objects keyed by group name -# enumdict - dictionary of EnumInfo objects keyed by enum name -# cmddict - dictionary of CmdInfo objects keyed by command name -# apidict - dictionary of Elements keyed by API name -# extensions - list of Elements -# extdict - dictionary of Elements keyed by extension name -# gen - OutputGenerator object used to write headers / messages -# genOpts - GeneratorOptions object used to control which -# fetures to write and how to format them -# emitFeatures - True to actually emit features for a version / extension, -# or False to just treat them as emitted -# Public methods -# loadElementTree(etree) - load registry from specified ElementTree -# loadFile(filename) - load registry from XML file -# setGenerator(gen) - OutputGenerator to use -# parseTree() - parse the registry once loaded & create dictionaries -# dumpReg(maxlen, filehandle) - diagnostic to dump the dictionaries -# to specified file handle (default stdout). Truncates type / -# enum / command elements to maxlen characters (default 80) -# generator(g) - specify the output generator object -# apiGen(apiname, genOpts) - generate API headers for the API type -# and profile specified in genOpts, but only for the versions and -# extensions specified there. -# apiReset() - call between calls to apiGen() to reset internal state -# Private methods -# addElementInfo(elem,info,infoName,dictionary) - add feature info to dict -# lookupElementInfo(fname,dictionary) - lookup feature info in dict -class Registry: - """Represents an API registry loaded from XML""" - def __init__(self): - self.tree = None - self.typedict = {} - self.groupdict = {} - self.enumdict = {} - self.cmddict = {} - self.apidict = {} - self.extensions = [] - self.extdict = {} - # A default output generator, so commands prior to apiGen can report - # errors via the generator object. - self.gen = OutputGenerator() - self.genOpts = None - self.emitFeatures = False - def loadElementTree(self, tree): - """Load ElementTree into a Registry object and parse it""" - self.tree = tree - self.parseTree() - def loadFile(self, file): - """Load an API registry XML file into a Registry object and parse it""" - self.tree = etree.parse(file) - self.parseTree() - def setGenerator(self, gen): - """Specify output generator object. None restores the default generator""" - self.gen = gen - self.gen.setRegistry(self) - - # addElementInfo - add information about an element to the - # corresponding dictionary - # elem - ///// Element - # info - corresponding {Type|Group|Enum|Cmd|Feature}Info object - # infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' - # dictionary - self.{type|group|enum|cmd|api|ext}dict - # If the Element has an 'api' attribute, the dictionary key is the - # tuple (name,api). If not, the key is the name. 'name' is an - # attribute of the Element - def addElementInfo(self, elem, info, infoName, dictionary): - if ('api' in elem.attrib): - key = (elem.get('name'),elem.get('api')) - else: - key = elem.get('name') - if key in dictionary: - self.gen.logMsg('warn', '*** Attempt to redefine', - infoName, 'with key:', key) - else: - dictionary[key] = info - # - # lookupElementInfo - find a {Type|Enum|Cmd}Info object by name. - # If an object qualified by API name exists, use that. - # fname - name of type / enum / command - # dictionary - self.{type|enum|cmd}dict - def lookupElementInfo(self, fname, dictionary): - key = (fname, self.genOpts.apiname) - if (key in dictionary): - # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) - return dictionary[key] - elif (fname in dictionary): - # self.gen.logMsg('diag', 'Found generic element for feature', fname) - return dictionary[fname] - else: - return None - def parseTree(self): - """Parse the registry Element, once created""" - # This must be the Element for the root - self.reg = self.tree.getroot() - # - # Create dictionary of registry types from toplevel tags - # and add 'name' attribute to each tag (where missing) - # based on its element. - # - # There's usually one block; more are OK - # Required attributes: 'name' or nested tag contents - self.typedict = {} - for type in self.reg.findall('types/type'): - # If the doesn't already have a 'name' attribute, set - # it from contents of its tag. - if (type.get('name') == None): - type.attrib['name'] = type.find('name').text - self.addElementInfo(type, TypeInfo(type), 'type', self.typedict) - # - # Create dictionary of registry enum groups from tags. - # - # Required attributes: 'name'. If no name is given, one is - # generated, but that group can't be identified and turned into an - # enum type definition - it's just a container for tags. - self.groupdict = {} - for group in self.reg.findall('enums'): - self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) - # - # Create dictionary of registry enums from tags - # - # tags usually define different namespaces for the values - # defined in those tags, but the actual names all share the - # same dictionary. - # Required attributes: 'name', 'value' - # For containing which have type="enum" or type="bitmask", - # tag all contained s are required. This is a stopgap until - # a better scheme for tagging core and extension enums is created. - self.enumdict = {} - for enums in self.reg.findall('enums'): - required = (enums.get('type') != None) - for enum in enums.findall('enum'): - enumInfo = EnumInfo(enum) - enumInfo.required = required - self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) - # - # Create dictionary of registry commands from tags - # and add 'name' attribute to each tag (where missing) - # based on its element. - # - # There's usually only one block; more are OK. - # Required attributes: 'name' or tag contents - self.cmddict = {} - for cmd in self.reg.findall('commands/command'): - # If the doesn't already have a 'name' attribute, set - # it from contents of its tag. - if (cmd.get('name') == None): - cmd.attrib['name'] = cmd.find('proto/name').text - ci = CmdInfo(cmd) - self.addElementInfo(cmd, ci, 'command', self.cmddict) - # - # Create dictionaries of API and extension interfaces - # from toplevel and tags. - # - self.apidict = {} - for feature in self.reg.findall('feature'): - featureInfo = FeatureInfo(feature) - self.addElementInfo(feature, featureInfo, 'feature', self.apidict) - self.extensions = self.reg.findall('extensions/extension') - self.extdict = {} - for feature in self.extensions: - featureInfo = FeatureInfo(feature) - self.addElementInfo(feature, featureInfo, 'extension', self.extdict) - - # Add additional enums defined only in tags - # to the corresponding core type. - # When seen here, the element, processed to contain the - # numeric enum value, is added to the corresponding - # element, as well as adding to the enum dictionary. It is - # *removed* from the element it is introduced in. - # Not doing this will cause spurious genEnum() - # calls to be made in output generation, and it's easier - # to handle here than in genEnum(). - # - # In lxml.etree, an Element can have only one parent, so the - # append() operation also removes the element. But in Python's - # ElementTree package, an Element can have multiple parents. So - # it must be explicitly removed from the tag, leading - # to the nested loop traversal of / elements - # below. - # - # This code also adds a 'extnumber' attribute containing the - # extension number, used for enumerant value calculation. - # - # For tags which are actually just constants, if there's - # no 'extends' tag but there is a 'value' or 'bitpos' tag, just - # add an EnumInfo record to the dictionary. That works because - # output generation of constants is purely dependency-based, and - # doesn't need to iterate through the XML tags. - # - # Something like this will need to be done for 'feature's up - # above, if we use the same mechanism for adding to the core - # API in 1.1. - # - for elem in feature.findall('require'): - for enum in elem.findall('enum'): - addEnumInfo = False - groupName = enum.get('extends') - if (groupName != None): - # self.gen.logMsg('diag', '*** Found extension enum', - # enum.get('name')) - # Add extension number attribute to the element - enum.attrib['extnumber'] = featureInfo.number - enum.attrib['extname'] = featureInfo.name - enum.attrib['supported'] = featureInfo.supported - # Look up the GroupInfo with matching groupName - if (groupName in self.groupdict.keys()): - # self.gen.logMsg('diag', '*** Matching group', - # groupName, 'found, adding element...') - gi = self.groupdict[groupName] - gi.elem.append(enum) - # Remove element from parent tag - # This should be a no-op in lxml.etree - elem.remove(enum) - else: - self.gen.logMsg('warn', '*** NO matching group', - groupName, 'for enum', enum.get('name'), 'found.') - addEnumInfo = True - elif (enum.get('value') or enum.get('bitpos')): - # self.gen.logMsg('diag', '*** Adding extension constant "enum"', - # enum.get('name')) - addEnumInfo = True - if (addEnumInfo): - enumInfo = EnumInfo(enum) - self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) - def dumpReg(self, maxlen = 40, filehandle = sys.stdout): - """Dump all the dictionaries constructed from the Registry object""" - write('***************************************', file=filehandle) - write(' ** Dumping Registry contents **', file=filehandle) - write('***************************************', file=filehandle) - write('// Types', file=filehandle) - for name in self.typedict: - tobj = self.typedict[name] - write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) - write('// Groups', file=filehandle) - for name in self.groupdict: - gobj = self.groupdict[name] - write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) - write('// Enums', file=filehandle) - for name in self.enumdict: - eobj = self.enumdict[name] - write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) - write('// Commands', file=filehandle) - for name in self.cmddict: - cobj = self.cmddict[name] - write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) - write('// APIs', file=filehandle) - for key in self.apidict: - write(' API Version ', key, '->', - etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) - write('// Extensions', file=filehandle) - for key in self.extdict: - write(' Extension', key, '->', - etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) - # write('***************************************', file=filehandle) - # write(' ** Dumping XML ElementTree **', file=filehandle) - # write('***************************************', file=filehandle) - # write(etree.tostring(self.tree.getroot(),pretty_print=True), file=filehandle) - # - # typename - name of type - # required - boolean (to tag features as required or not) - def markTypeRequired(self, typename, required): - """Require (along with its dependencies) or remove (but not its dependencies) a type""" - self.gen.logMsg('diag', '*** tagging type:', typename, '-> required =', required) - # Get TypeInfo object for tag corresponding to typename - type = self.lookupElementInfo(typename, self.typedict) - if (type != None): - if (required): - # Tag type dependencies in 'required' attributes as - # required. This DOES NOT un-tag dependencies in a - # tag. See comments in markRequired() below for the reason. - if ('requires' in type.elem.attrib): - depType = type.elem.get('requires') - self.gen.logMsg('diag', '*** Generating dependent type', - depType, 'for type', typename) - self.markTypeRequired(depType, required) - # Tag types used in defining this type (e.g. in nested - # tags) - # Look for in entire tree, - # not just immediate children - for subtype in type.elem.findall('.//type'): - self.gen.logMsg('diag', '*** markRequired: type requires dependent ', subtype.text) - self.markTypeRequired(subtype.text, required) - # Tag enums used in defining this type, for example in - # member[MEMBER_SIZE] - for subenum in type.elem.findall('.//enum'): - self.gen.logMsg('diag', '*** markRequired: type requires dependent ', subenum.text) - self.markEnumRequired(subenum.text, required) - type.required = required - else: - self.gen.logMsg('warn', '*** type:', typename , 'IS NOT DEFINED') - # - # enumname - name of enum - # required - boolean (to tag features as required or not) - def markEnumRequired(self, enumname, required): - self.gen.logMsg('diag', '*** tagging enum:', enumname, '-> required =', required) - enum = self.lookupElementInfo(enumname, self.enumdict) - if (enum != None): - enum.required = required - else: - self.gen.logMsg('warn', '*** enum:', enumname , 'IS NOT DEFINED') - # - # features - Element for or tag - # required - boolean (to tag features as required or not) - def markRequired(self, features, required): - """Require or remove features specified in the Element""" - self.gen.logMsg('diag', '*** markRequired (features = , required =', required, ')') - # Loop over types, enums, and commands in the tag - # @@ It would be possible to respect 'api' and 'profile' attributes - # in individual features, but that's not done yet. - for typeElem in features.findall('type'): - self.markTypeRequired(typeElem.get('name'), required) - for enumElem in features.findall('enum'): - self.markEnumRequired(enumElem.get('name'), required) - for cmdElem in features.findall('command'): - name = cmdElem.get('name') - self.gen.logMsg('diag', '*** tagging command:', name, '-> required =', required) - cmd = self.lookupElementInfo(name, self.cmddict) - if (cmd != None): - cmd.required = required - # Tag all parameter types of this command as required. - # This DOES NOT remove types of commands in a - # tag, because many other commands may use the same type. - # We could be more clever and reference count types, - # instead of using a boolean. - if (required): - # Look for in entire tree, - # not just immediate children - for type in cmd.elem.findall('.//type'): - self.gen.logMsg('diag', '*** markRequired: command implicitly requires dependent type', type.text) - self.markTypeRequired(type.text, required) - else: - self.gen.logMsg('warn', '*** command:', name, 'IS NOT DEFINED') - # - # interface - Element for or , containing - # and tags - # api - string specifying API name being generated - # profile - string specifying API profile being generated - def requireAndRemoveFeatures(self, interface, api, profile): - """Process and tags for a or """ - # marks things that are required by this version/profile - for feature in interface.findall('require'): - if (matchAPIProfile(api, profile, feature)): - self.markRequired(feature,True) - # marks things that are removed by this version/profile - for feature in interface.findall('remove'): - if (matchAPIProfile(api, profile, feature)): - self.markRequired(feature,False) - - def assignAdditionalValidity(self, interface, api, profile): - # - # Loop over all usage inside all tags. - for feature in interface.findall('require'): - if (matchAPIProfile(api, profile, feature)): - for v in feature.findall('usage'): - if v.get('command'): - self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v)) - if v.get('struct'): - self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v)) - - # - # Loop over all usage inside all tags. - for feature in interface.findall('remove'): - if (matchAPIProfile(api, profile, feature)): - for v in feature.findall('usage'): - if v.get('command'): - self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v)) - if v.get('struct'): - self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v)) - - # - # generateFeature - generate a single type / enum group / enum / command, - # and all its dependencies as needed. - # fname - name of feature (//) - # ftype - type of feature, 'type' | 'enum' | 'command' - # dictionary - of *Info objects - self.{type|enum|cmd}dict - def generateFeature(self, fname, ftype, dictionary): - f = self.lookupElementInfo(fname, dictionary) - if (f == None): - # No such feature. This is an error, but reported earlier - self.gen.logMsg('diag', '*** No entry found for feature', fname, - 'returning!') - return - # - # If feature isn't required, or has already been declared, return - if (not f.required): - self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(not required)') - return - if (f.declared): - self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(already declared)') - return - # Always mark feature declared, as though actually emitted - f.declared = True - # - # Pull in dependent declaration(s) of the feature. - # For types, there may be one type in the 'required' attribute of - # the element, as well as many in imbedded and tags - # within the element. - # For commands, there may be many in tags within the element. - # For enums, no dependencies are allowed (though perhaps if you - # have a uint64 enum, it should require that type). - genProc = None - if (ftype == 'type'): - genProc = self.gen.genType - if ('requires' in f.elem.attrib): - depname = f.elem.get('requires') - self.gen.logMsg('diag', '*** Generating required dependent type', - depname) - self.generateFeature(depname, 'type', self.typedict) - for subtype in f.elem.findall('.//type'): - self.gen.logMsg('diag', '*** Generating required dependent ', - subtype.text) - self.generateFeature(subtype.text, 'type', self.typedict) - for subtype in f.elem.findall('.//enum'): - self.gen.logMsg('diag', '*** Generating required dependent ', - subtype.text) - self.generateFeature(subtype.text, 'enum', self.enumdict) - # If the type is an enum group, look up the corresponding - # group in the group dictionary and generate that instead. - if (f.elem.get('category') == 'enum'): - self.gen.logMsg('diag', '*** Type', fname, 'is an enum group, so generate that instead') - group = self.lookupElementInfo(fname, self.groupdict) - if (group == None): - # Unless this is tested for, it's probably fatal to call below - genProc = None - self.logMsg('warn', '*** NO MATCHING ENUM GROUP FOUND!!!') - else: - genProc = self.gen.genGroup - f = group - elif (ftype == 'command'): - genProc = self.gen.genCmd - for type in f.elem.findall('.//type'): - depname = type.text - self.gen.logMsg('diag', '*** Generating required parameter type', - depname) - self.generateFeature(depname, 'type', self.typedict) - elif (ftype == 'enum'): - genProc = self.gen.genEnum - # Actually generate the type only if emitting declarations - if self.emitFeatures: - self.gen.logMsg('diag', '*** Emitting', ftype, 'decl for', fname) - genProc(f, fname) - else: - self.gen.logMsg('diag', '*** Skipping', ftype, fname, - '(not emitting this feature)') - # - # generateRequiredInterface - generate all interfaces required - # by an API version or extension - # interface - Element for or - def generateRequiredInterface(self, interface): - """Generate required C interface for specified API version/extension""" - - # - # Loop over all features inside all tags. - for features in interface.findall('require'): - for t in features.findall('type'): - self.generateFeature(t.get('name'), 'type', self.typedict) - for e in features.findall('enum'): - self.generateFeature(e.get('name'), 'enum', self.enumdict) - for c in features.findall('command'): - self.generateFeature(c.get('name'), 'command', self.cmddict) - - # - # apiGen(genOpts) - generate interface for specified versions - # genOpts - GeneratorOptions object with parameters used - # by the Generator object. - def apiGen(self, genOpts): - """Generate interfaces for the specified API type and range of versions""" - # - self.gen.logMsg('diag', '*******************************************') - self.gen.logMsg('diag', ' Registry.apiGen file:', genOpts.filename, - 'api:', genOpts.apiname, - 'profile:', genOpts.profile) - self.gen.logMsg('diag', '*******************************************') - # - self.genOpts = genOpts - # Reset required/declared flags for all features - self.apiReset() - # - # Compile regexps used to select versions & extensions - regVersions = re.compile(self.genOpts.versions) - regEmitVersions = re.compile(self.genOpts.emitversions) - regAddExtensions = re.compile(self.genOpts.addExtensions) - regRemoveExtensions = re.compile(self.genOpts.removeExtensions) - # - # Get all matching API versions & add to list of FeatureInfo - features = [] - apiMatch = False - for key in self.apidict: - fi = self.apidict[key] - api = fi.elem.get('api') - if (api == self.genOpts.apiname): - apiMatch = True - if (regVersions.match(fi.version)): - # Matches API & version #s being generated. Mark for - # emission and add to the features[] list . - # @@ Could use 'declared' instead of 'emit'? - fi.emit = (regEmitVersions.match(fi.version) != None) - features.append(fi) - if (not fi.emit): - self.gen.logMsg('diag', '*** NOT tagging feature api =', api, - 'name =', fi.name, 'version =', fi.version, - 'for emission (does not match emitversions pattern)') - else: - self.gen.logMsg('diag', '*** NOT including feature api =', api, - 'name =', fi.name, 'version =', fi.version, - '(does not match requested versions)') - else: - self.gen.logMsg('diag', '*** NOT including feature api =', api, - 'name =', fi.name, - '(does not match requested API)') - if (not apiMatch): - self.gen.logMsg('warn', '*** No matching API versions found!') - # - # Get all matching extensions, in order by their extension number, - # and add to the list of features. - # Start with extensions tagged with 'api' pattern matching the API - # being generated. Add extensions matching the pattern specified in - # regExtensions, then remove extensions matching the pattern - # specified in regRemoveExtensions - for (extName,ei) in sorted(self.extdict.items(),key = lambda x : x[1].number): - extName = ei.name - include = False - # - # Include extension if defaultExtensions is not None and if the - # 'supported' attribute matches defaultExtensions. The regexp in - # 'supported' must exactly match defaultExtensions, so bracket - # it with ^(pat)$. - pat = '^(' + ei.elem.get('supported') + ')$' - if (self.genOpts.defaultExtensions and - re.match(pat, self.genOpts.defaultExtensions)): - self.gen.logMsg('diag', '*** Including extension', - extName, "(defaultExtensions matches the 'supported' attribute)") - include = True - # - # Include additional extensions if the extension name matches - # the regexp specified in the generator options. This allows - # forcing extensions into an interface even if they're not - # tagged appropriately in the registry. - if (regAddExtensions.match(extName) != None): - self.gen.logMsg('diag', '*** Including extension', - extName, '(matches explicitly requested extensions to add)') - include = True - # Remove extensions if the name matches the regexp specified - # in generator options. This allows forcing removal of - # extensions from an interface even if they're tagged that - # way in the registry. - if (regRemoveExtensions.match(extName) != None): - self.gen.logMsg('diag', '*** Removing extension', - extName, '(matches explicitly requested extensions to remove)') - include = False - # - # If the extension is to be included, add it to the - # extension features list. - if (include): - ei.emit = True - features.append(ei) - else: - self.gen.logMsg('diag', '*** NOT including extension', - extName, '(does not match api attribute or explicitly requested extensions)') - # - # Sort the extension features list, if a sort procedure is defined - if (self.genOpts.sortProcedure): - self.genOpts.sortProcedure(features) - # - # Pass 1: loop over requested API versions and extensions tagging - # types/commands/features as required (in an block) or no - # longer required (in an block). It is possible to remove - # a feature in one version and restore it later by requiring it in - # a later version. - # If a profile other than 'None' is being generated, it must - # match the profile attribute (if any) of the and - # tags. - self.gen.logMsg('diag', '*** PASS 1: TAG FEATURES ********************************************') - for f in features: - self.gen.logMsg('diag', '*** PASS 1: Tagging required and removed features for', - f.name) - self.requireAndRemoveFeatures(f.elem, self.genOpts.apiname, self.genOpts.profile) - self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile) - # - # Pass 2: loop over specified API versions and extensions printing - # declarations for required things which haven't already been - # generated. - self.gen.logMsg('diag', '*** PASS 2: GENERATE INTERFACES FOR FEATURES ************************') - self.gen.beginFile(self.genOpts) - for f in features: - self.gen.logMsg('diag', '*** PASS 2: Generating interface for', - f.name) - emit = self.emitFeatures = f.emit - if (not emit): - self.gen.logMsg('diag', '*** PASS 2: NOT declaring feature', - f.elem.get('name'), 'because it is not tagged for emission') - # Generate the interface (or just tag its elements as having been - # emitted, if they haven't been). - self.gen.beginFeature(f.elem, emit) - self.generateRequiredInterface(f.elem) - self.gen.endFeature() - self.gen.endFile() - # - # apiReset - use between apiGen() calls to reset internal state - # - def apiReset(self): - """Reset type/enum/command dictionaries before generating another API""" - for type in self.typedict: - self.typedict[type].resetState() - for enum in self.enumdict: - self.enumdict[enum].resetState() - for cmd in self.cmddict: - self.cmddict[cmd].resetState() - for cmd in self.apidict: - self.apidict[cmd].resetState() - # - # validateGroups - check that group= attributes match actual groups - # - def validateGroups(self): - """Validate group= attributes on and tags""" - # Keep track of group names not in tags - badGroup = {} - self.gen.logMsg('diag', '*** VALIDATING GROUP ATTRIBUTES ***') - for cmd in self.reg.findall('commands/command'): - proto = cmd.find('proto') - funcname = cmd.find('proto/name').text - if ('group' in proto.attrib.keys()): - group = proto.get('group') - # self.gen.logMsg('diag', '*** Command ', funcname, ' has return group ', group) - if (group not in self.groupdict.keys()): - # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group) - if (group not in badGroup.keys()): - badGroup[group] = 1 - else: - badGroup[group] = badGroup[group] + 1 - for param in cmd.findall('param'): - pname = param.find('name') - if (pname != None): - pname = pname.text - else: - pname = type.get('name') - if ('group' in param.attrib.keys()): - group = param.get('group') - if (group not in self.groupdict.keys()): - # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group) - if (group not in badGroup.keys()): - badGroup[group] = 1 - else: - badGroup[group] = badGroup[group] + 1 - if (len(badGroup.keys()) > 0): - self.gen.logMsg('diag', '*** SUMMARY OF UNRECOGNIZED GROUPS ***') - for key in sorted(badGroup.keys()): - self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times') diff --git a/scripts/generator.py b/scripts/generator.py new file mode 100755 index 00000000..043121cd --- /dev/null +++ b/scripts/generator.py @@ -0,0 +1,498 @@ +#!/usr/bin/python3 -i +# +# Copyright (c) 2013-2016 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 os,re,sys + +def write( *args, **kwargs ): + file = kwargs.pop('file',sys.stdout) + end = kwargs.pop( 'end','\n') + file.write( ' '.join([str(arg) for arg in args]) ) + file.write( end ) + +# noneStr - returns string argument, or "" if argument is None. +# Used in converting etree Elements into text. +# str - string to convert +def noneStr(str): + if (str): + return str + else: + return "" + +# enquote - returns string argument with surrounding quotes, +# for serialization into Python code. +def enquote(str): + if (str): + return "'" + str + "'" + else: + return None + +# apiName - returns True if name is a Vulkan name (vk/Vk/VK prefix, or a +# function pointer type), False otherwise. +def apiName(str): + return str[0:2].lower() == 'vk' or str[0:3] == 'PFN' + +# Primary sort key for regSortFeatures. +# Sorts by category of the feature name string: +# Core API features (those defined with a tag) +# ARB/KHR/OES (Khronos extensions) +# other (EXT/vendor extensions) +# This will need changing for Vulkan! +def regSortCategoryKey(feature): + if (feature.elem.tag == 'feature'): + return 0 + elif (feature.category == 'ARB' or + feature.category == 'KHR' or + feature.category == 'OES'): + return 1 + else: + return 2 + +# Secondary sort key for regSortFeatures. +# Sorts by extension name. +def regSortNameKey(feature): + return feature.name + +# Second sort key for regSortFeatures. +# Sorts by feature version. elements all have version number "0" +def regSortFeatureVersionKey(feature): + return float(feature.version) + +# Tertiary sort key for regSortFeatures. +# Sorts by extension number. elements all have extension number 0. +def regSortExtensionNumberKey(feature): + return int(feature.number) + +# regSortFeatures - default sort procedure for features. +# Sorts by primary key of feature category ('feature' or 'extension') +# then by version number (for features) +# then by extension number (for extensions) +def regSortFeatures(featureList): + featureList.sort(key = regSortExtensionNumberKey) + featureList.sort(key = regSortFeatureVersionKey) + featureList.sort(key = regSortCategoryKey) + +# GeneratorOptions - base class for options used during header production +# These options are target language independent, and used by +# Registry.apiGen() and by base OutputGenerator objects. +# +# Members +# filename - basename of file to generate, or None to write to stdout. +# directory - directory in which to generate filename +# apiname - string matching 'apiname' attribute, e.g. 'gl'. +# profile - string specifying API profile , e.g. 'core', or None. +# versions - regex matching API versions to process interfaces for. +# Normally '.*' or '[0-9]\.[0-9]' to match all defined versions. +# emitversions - regex matching API versions to actually emit +# interfaces for (though all requested versions are considered +# when deciding which interfaces to generate). For GL 4.3 glext.h, +# this might be '1\.[2-5]|[2-4]\.[0-9]'. +# defaultExtensions - If not None, a string which must in its +# entirety match the pattern in the "supported" attribute of +# the . Defaults to None. Usually the same as apiname. +# addExtensions - regex matching names of additional extensions +# to include. Defaults to None. +# removeExtensions - regex matching names of extensions to +# remove (after defaultExtensions and addExtensions). Defaults +# to None. +# sortProcedure - takes a list of FeatureInfo objects and sorts +# them in place to a preferred order in the generated output. +# Default is core API versions, ARB/KHR/OES extensions, all +# other extensions, alphabetically within each group. +# The regex patterns can be None or empty, in which case they match +# nothing. +class GeneratorOptions: + """Represents options during header production from an API registry""" + def __init__(self, + filename = None, + directory = '.', + apiname = None, + profile = None, + versions = '.*', + emitversions = '.*', + defaultExtensions = None, + addExtensions = None, + removeExtensions = None, + sortProcedure = regSortFeatures): + self.filename = filename + self.directory = directory + self.apiname = apiname + self.profile = profile + self.versions = self.emptyRegex(versions) + self.emitversions = self.emptyRegex(emitversions) + self.defaultExtensions = defaultExtensions + self.addExtensions = self.emptyRegex(addExtensions) + self.removeExtensions = self.emptyRegex(removeExtensions) + self.sortProcedure = sortProcedure + # + # Substitute a regular expression which matches no version + # or extension names for None or the empty string. + def emptyRegex(self,pat): + if (pat == None or pat == ''): + return '_nomatch_^' + else: + return pat + +# OutputGenerator - base class for generating API interfaces. +# Manages basic logic, logging, and output file control +# Derived classes actually generate formatted output. +# +# ---- methods ---- +# OutputGenerator(errFile, warnFile, diagFile) +# errFile, warnFile, diagFile - file handles to write errors, +# warnings, diagnostics to. May be None to not write. +# logMsg(level, *args) - log messages of different categories +# level - 'error', 'warn', or 'diag'. 'error' will also +# raise a UserWarning exception +# *args - print()-style arguments +# setExtMap(map) - specify a dictionary map from extension names to +# numbers, used in creating values for extension enumerants. +# makeDir(directory) - create a directory, if not already done. +# Generally called from derived generators creating hierarchies. +# beginFile(genOpts) - start a new interface file +# genOpts - GeneratorOptions controlling what's generated and how +# endFile() - finish an interface file, closing it when done +# beginFeature(interface, emit) - write interface for a feature +# and tag generated features as having been done. +# interface - element for the / to generate +# emit - actually write to the header only when True +# endFeature() - finish an interface. +# genType(typeinfo,name) - generate interface for a type +# typeinfo - TypeInfo for a type +# genStruct(typeinfo,name) - generate interface for a C "struct" type. +# typeinfo - TypeInfo for a type interpreted as a struct +# genGroup(groupinfo,name) - generate interface for a group of enums (C "enum") +# groupinfo - GroupInfo for a group +# genEnum(enuminfo, name) - generate interface for an enum (constant) +# enuminfo - EnumInfo for an enum +# name - enum name +# genCmd(cmdinfo) - generate interface for a command +# cmdinfo - CmdInfo for a command +# isEnumRequired(enumElem) - return True if this element is required +# elem - element to test +# makeCDecls(cmd) - return C prototype and function pointer typedef for a +# Element, as a list of two strings +# cmd - Element for the +# newline() - print a newline to the output file (utility function) +# +class OutputGenerator: + """Generate specified API interfaces in a specific style, such as a C header""" + # + # categoryToPath - map XML 'category' to include file directory name + categoryToPath = { + 'bitmask' : 'flags', + 'enum' : 'enums', + 'funcpointer' : 'funcpointers', + 'handle' : 'handles', + 'define' : 'defines', + 'basetype' : 'basetypes', + } + # + # Constructor + def __init__(self, + errFile = sys.stderr, + warnFile = sys.stderr, + diagFile = sys.stdout): + self.outFile = None + self.errFile = errFile + self.warnFile = warnFile + self.diagFile = diagFile + # Internal state + self.featureName = None + self.genOpts = None + self.registry = None + # Used for extension enum value generation + self.extBase = 1000000000 + self.extBlockSize = 1000 + self.madeDirs = {} + # + # logMsg - write a message of different categories to different + # destinations. + # level - + # 'diag' (diagnostic, voluminous) + # 'warn' (warning) + # 'error' (fatal error - raises exception after logging) + # *args - print()-style arguments to direct to corresponding log + def logMsg(self, level, *args): + """Log a message at the given level. Can be ignored or log to a file""" + if (level == 'error'): + strfile = io.StringIO() + write('ERROR:', *args, file=strfile) + if (self.errFile != None): + write(strfile.getvalue(), file=self.errFile) + raise UserWarning(strfile.getvalue()) + elif (level == 'warn'): + if (self.warnFile != None): + write('WARNING:', *args, file=self.warnFile) + elif (level == 'diag'): + if (self.diagFile != None): + write('DIAG:', *args, file=self.diagFile) + else: + raise UserWarning( + '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) + # + # enumToValue - parses and converts an tag into a value. + # Returns a list + # first element - integer representation of the value, or None + # if needsNum is False. The value must be a legal number + # if needsNum is True. + # second element - string representation of the value + # There are several possible representations of values. + # A 'value' attribute simply contains the value. + # A 'bitpos' attribute defines a value by specifying the bit + # position which is set in that value. + # A 'offset','extbase','extends' triplet specifies a value + # as an offset to a base value defined by the specified + # 'extbase' extension name, which is then cast to the + # typename specified by 'extends'. This requires probing + # the registry database, and imbeds knowledge of the + # Vulkan extension enum scheme in this function. + def enumToValue(self, elem, needsNum): + name = elem.get('name') + numVal = None + if ('value' in elem.keys()): + value = elem.get('value') + # print('About to translate value =', value, 'type =', type(value)) + if (needsNum): + numVal = int(value, 0) + # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or + # 'ull'), append it to the string value. + # t = enuminfo.elem.get('type') + # if (t != None and t != '' and t != 'i' and t != 's'): + # value += enuminfo.type + self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']') + return [numVal, value] + if ('bitpos' in elem.keys()): + value = elem.get('bitpos') + numVal = int(value, 0) + numVal = 1 << numVal + value = '0x%08x' % numVal + self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') + return [numVal, value] + if ('offset' in elem.keys()): + # Obtain values in the mapping from the attributes + enumNegative = False + offset = int(elem.get('offset'),0) + extnumber = int(elem.get('extnumber'),0) + extends = elem.get('extends') + if ('dir' in elem.keys()): + enumNegative = True + self.logMsg('diag', 'Enum', name, 'offset =', offset, + 'extnumber =', extnumber, 'extends =', extends, + 'enumNegative =', enumNegative) + # Now determine the actual enumerant value, as defined + # in the "Layers and Extensions" appendix of the spec. + numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset + if (enumNegative): + numVal = -numVal + value = '%d' % numVal + # More logic needed! + self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') + return [numVal, value] + return [None, None] + # + def makeDir(self, path): + self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') + if not (path in self.madeDirs.keys()): + # This can get race conditions with multiple writers, see + # https://stackoverflow.com/questions/273192/ + if not os.path.exists(path): + os.makedirs(path) + self.madeDirs[path] = None + # + def beginFile(self, genOpts): + self.genOpts = genOpts + # + # Open specified output file. Not done in constructor since a + # Generator can be used without writing to a file. + if (self.genOpts.filename != None): + self.outFile = open(self.genOpts.directory + '/' + self.genOpts.filename, 'w') + else: + self.outFile = sys.stdout + def endFile(self): + self.errFile and self.errFile.flush() + self.warnFile and self.warnFile.flush() + self.diagFile and self.diagFile.flush() + self.outFile.flush() + if (self.outFile != sys.stdout and self.outFile != sys.stderr): + self.outFile.close() + self.genOpts = None + # + def beginFeature(self, interface, emit): + self.emit = emit + self.featureName = interface.get('name') + # If there's an additional 'protect' attribute in the feature, save it + self.featureExtraProtect = interface.get('protect') + def endFeature(self): + # Derived classes responsible for emitting feature + self.featureName = None + self.featureExtraProtect = None + # Utility method to validate we're generating something only inside a + # tag + def validateFeature(self, featureType, featureName): + if (self.featureName == None): + raise UserWarning('Attempt to generate', featureType, name, + 'when not in feature') + # + # Type generation + def genType(self, typeinfo, name): + self.validateFeature('type', name) + # + # Struct (e.g. C "struct" type) generation + def genStruct(self, typeinfo, name): + self.validateFeature('struct', name) + # + # Group (e.g. C "enum" type) generation + def genGroup(self, groupinfo, name): + self.validateFeature('group', name) + # + # Enumerant (really, constant) generation + def genEnum(self, enuminfo, name): + self.validateFeature('enum', name) + # + # Command generation + def genCmd(self, cmd, name): + self.validateFeature('command', name) + # + # Utility functions - turn a into C-language prototype + # and typedef declarations for that name. + # name - contents of tag + # tail - whatever text follows that tag in the Element + def makeProtoName(self, name, tail): + return self.genOpts.apientry + name + tail + def makeTypedefName(self, name, tail): + return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' + # + # makeCParamDecl - return a string which is an indented, formatted + # declaration for a or block (e.g. function parameter + # or structure/union member). + # param - Element ( or ) to format + # aligncol - if non-zero, attempt to align the nested element + # at this column + def makeCParamDecl(self, param, aligncol): + paramdecl = ' ' + noneStr(param.text) + for elem in param: + text = noneStr(elem.text) + tail = noneStr(elem.tail) + if (elem.tag == 'name' and aligncol > 0): + self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) + # Align at specified column, if possible + paramdecl = paramdecl.rstrip() + oldLen = len(paramdecl) + # This works around a problem where very long type names - + # longer than the alignment column - would run into the tail + # text. + paramdecl = paramdecl.ljust(aligncol-1) + ' ' + newLen = len(paramdecl) + self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) + paramdecl += text + tail + return paramdecl + # + # getCParamTypeLength - return the length of the type field is an indented, formatted + # declaration for a or block (e.g. function parameter + # or structure/union member). + # param - Element ( or ) to identify + def getCParamTypeLength(self, param): + paramdecl = ' ' + noneStr(param.text) + for elem in param: + text = noneStr(elem.text) + tail = noneStr(elem.tail) + if (elem.tag == 'name'): + # Align at specified column, if possible + newLen = len(paramdecl.rstrip()) + self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) + paramdecl += text + tail + return newLen + # + # isEnumRequired(elem) - return True if this element is + # required, False otherwise + # elem - element to test + def isEnumRequired(self, elem): + return (elem.get('extname') is None or + re.match(self.genOpts.addExtensions, elem.get('extname')) is not None or + self.genOpts.defaultExtensions == elem.get('supported')) + # + # makeCDecls - return C prototype and function pointer typedef for a + # command, as a two-element list of strings. + # cmd - Element containing a tag + def makeCDecls(self, cmd): + """Generate C function pointer typedef for Element""" + proto = cmd.find('proto') + params = cmd.findall('param') + # Begin accumulating prototype and typedef strings + pdecl = self.genOpts.apicall + tdecl = 'typedef ' + # + # Insert the function return type/name. + # For prototypes, add APIENTRY macro before the name + # For typedefs, add (APIENTRY *) around the name and + # use the PFN_cmdnameproc naming convention. + # Done by walking the tree for element by element. + # etree has elem.text followed by (elem[i], elem[i].tail) + # for each child element and any following text + # Leading text + pdecl += noneStr(proto.text) + tdecl += noneStr(proto.text) + # For each child element, if it's a wrap in appropriate + # declaration. Otherwise append its contents and tail contents. + for elem in proto: + text = noneStr(elem.text) + tail = noneStr(elem.tail) + if (elem.tag == 'name'): + pdecl += self.makeProtoName(text, tail) + tdecl += self.makeTypedefName(text, tail) + else: + pdecl += text + tail + tdecl += text + tail + # Now add the parameter declaration list, which is identical + # for prototypes and typedefs. Concatenate all the text from + # a node without the tags. No tree walking required + # since all tags are ignored. + # Uses: self.indentFuncProto + # self.indentFuncPointer + # self.alignFuncParam + # Might be able to doubly-nest the joins, e.g. + # ','.join(('_'.join([l[i] for i in range(0,len(l))]) + n = len(params) + # Indented parameters + if n > 0: + indentdecl = '(\n' + for i in range(0,n): + paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam) + if (i < n - 1): + paramdecl += ',\n' + else: + paramdecl += ');' + indentdecl += paramdecl + else: + indentdecl = '(void);' + # Non-indented parameters + paramdecl = '(' + if n > 0: + for i in range(0,n): + paramdecl += ''.join([t for t in params[i].itertext()]) + if (i < n - 1): + paramdecl += ', ' + else: + paramdecl += 'void' + paramdecl += ");"; + return [ pdecl + indentdecl, tdecl + paramdecl ] + # + def newline(self): + write('', file=self.outFile) + + def setRegistry(self, registry): + self.registry = registry + # diff --git a/scripts/lvl_genvk.py b/scripts/lvl_genvk.py new file mode 100644 index 00000000..8fa7c1bf --- /dev/null +++ b/scripts/lvl_genvk.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# +# Copyright (c) 2013-2016 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 argparse, cProfile, pdb, string, sys, time +from reg import * +from generator import write + +# +# LoaderAndValidationLayer Generator Additions +from threading_generator import ThreadGeneratorOptions, ThreadOutputGenerator +from parameter_validation_generator import ParamCheckerGeneratorOptions, ParamCheckerOutputGenerator +from unique_objects_generator import UniqueObjectsGeneratorOptions, UniqueObjectsOutputGenerator + +# Simple timer functions +startTime = None + +def startTimer(timeit): + global startTime + startTime = time.clock() + +def endTimer(timeit, msg): + global startTime + endTime = time.clock() + if (timeit): + write(msg, endTime - startTime, file=sys.stderr) + startTime = None + +# Turn a list of strings into a regexp string matching exactly those strings +def makeREstring(list): + return '^(' + '|'.join(list) + ')$' + +# Returns a directory of [ generator function, generator options ] indexed +# by specified short names. The generator options incorporate the following +# parameters: +# +# extensions - list of extension names to include. +# protect - True if re-inclusion protection should be added to headers +# directory - path to directory in which to generate the target(s) +def makeGenOpts(extensions = [], protect = True, directory = '.'): + global genOpts + genOpts = {} + + # Descriptive names for various regexp patterns used to select + # versions and extensions + allVersions = allExtensions = '.*' + noVersions = noExtensions = None + + addExtensions = makeREstring(extensions) + removeExtensions = makeREstring([]) + + # Copyright text prefixing all headers (list of strings). + prefixStrings = [ + '/*', + '** Copyright (c) 2015-2016 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.', + '*/', + '' + ] + + # Text specific to Vulkan headers + vkPrefixStrings = [ + '/*', + '** This header is generated from the Khronos Vulkan XML API Registry.', + '**', + '*/', + '' + ] + + # Defaults for generating re-inclusion protection wrappers (or not) + protectFile = protect + protectFeature = protect + protectProto = protect + + # + # LoaderAndValidationLayer Generators + # Options for threading layer + genOpts['thread_check.h'] = [ + ThreadOutputGenerator, + ThreadGeneratorOptions( + filename = 'thread_check.h', + directory = directory, + apiname = 'vulkan', + profile = None, + versions = allVersions, + emitversions = allVersions, + defaultExtensions = 'vulkan', + addExtensions = addExtensions, + removeExtensions = removeExtensions, + prefixText = prefixStrings + vkPrefixStrings, + protectFeature = False, + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48) + ] + + # Options for parameter validation layer + genOpts['parameter_validation.h'] = [ + ParamCheckerOutputGenerator, + ParamCheckerGeneratorOptions( + filename = 'parameter_validation.h', + directory = directory, + apiname = 'vulkan', + profile = None, + versions = allVersions, + emitversions = allVersions, + defaultExtensions = 'vulkan', + addExtensions = addExtensions, + removeExtensions = removeExtensions, + prefixText = prefixStrings + vkPrefixStrings, + protectFeature = False, + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48) + ] + + # Options for unique objects layer + genOpts['unique_objects_wrappers.h'] = [ + UniqueObjectsOutputGenerator, + UniqueObjectsGeneratorOptions( + filename = 'unique_objects_wrappers.h', + directory = directory, + apiname = 'vulkan', + profile = None, + versions = allVersions, + emitversions = allVersions, + defaultExtensions = 'vulkan', + addExtensions = addExtensions, + removeExtensions = removeExtensions, + prefixText = prefixStrings + vkPrefixStrings, + protectFeature = False, + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48) + ] + +# Generate a target based on the options in the matching genOpts{} object. +# This is encapsulated in a function so it can be profiled and/or timed. +# The args parameter is an parsed argument object containing the following +# fields that are used: +# target - target to generate +# directory - directory to generate it in +# protect - True if re-inclusion wrappers should be created +# extensions - list of additional extensions to include in generated +# interfaces +def genTarget(args): + global genOpts + + # Create generator options with specified parameters + makeGenOpts(extensions = args.extension, + protect = args.protect, + directory = args.directory) + + if (args.target in genOpts.keys()): + createGenerator = genOpts[args.target][0] + options = genOpts[args.target][1] + + write('* Building', options.filename, file=sys.stderr) + + startTimer(args.time) + gen = createGenerator(errFile=errWarn, + warnFile=errWarn, + diagFile=diag) + reg.setGenerator(gen) + reg.apiGen(options) + write('* Generated', options.filename, file=sys.stderr) + endTimer(args.time, '* Time to generate ' + options.filename + ' =') + else: + write('No generator options for unknown target:', + args.target, file=sys.stderr) + +# -extension name - may be a single extension name, a a space-separated list +# of names, or a regular expression. +if __name__ == '__main__': + parser = argparse.ArgumentParser() + + parser.add_argument('-extension', action='append', + default=[], + help='Specify an extension or extensions to add to targets') + parser.add_argument('-debug', action='store_true', + help='Enable debugging') + parser.add_argument('-dump', action='store_true', + help='Enable dump to stderr') + parser.add_argument('-diagfile', action='store', + default=None, + help='Write diagnostics to specified file') + parser.add_argument('-errfile', action='store', + default=None, + help='Write errors and warnings to specified file instead of stderr') + parser.add_argument('-noprotect', dest='protect', action='store_false', + help='Disable inclusion protection in output headers') + parser.add_argument('-profile', action='store_true', + help='Enable profiling') + parser.add_argument('-registry', action='store', + default='vk.xml', + help='Use specified registry file instead of vk.xml') + parser.add_argument('-time', action='store_true', + help='Enable timing') + parser.add_argument('-validate', action='store_true', + help='Enable group validation') + parser.add_argument('-o', action='store', dest='directory', + default='.', + help='Create target and related files in specified directory') + parser.add_argument('target', metavar='target', nargs='?', + help='Specify target') + + args = parser.parse_args() + + # This splits arguments which are space-separated lists + args.extension = [name for arg in args.extension for name in arg.split()] + + # Load & parse registry + reg = Registry() + + startTimer(args.time) + tree = etree.parse(args.registry) + endTimer(args.time, '* Time to make ElementTree =') + + startTimer(args.time) + reg.loadElementTree(tree) + endTimer(args.time, '* Time to parse ElementTree =') + + if (args.validate): + reg.validateGroups() + + if (args.dump): + write('* Dumping registry to regdump.txt', file=sys.stderr) + reg.dumpReg(filehandle = open('regdump.txt','w')) + + # create error/warning & diagnostic files + if (args.errfile): + errWarn = open(args.errfile, 'w') + else: + errWarn = sys.stderr + + if (args.diagfile): + diag = open(args.diagfile, 'w') + else: + diag = None + + if (args.debug): + pdb.run('genTarget(args)') + elif (args.profile): + import cProfile, pstats + cProfile.run('genTarget(args)', 'profile.txt') + p = pstats.Stats('profile.txt') + p.strip_dirs().sort_stats('time').print_stats(50) + else: + genTarget(args) diff --git a/scripts/parameter_validation_generator.py b/scripts/parameter_validation_generator.py new file mode 100644 index 00000000..e0d215da --- /dev/null +++ b/scripts/parameter_validation_generator.py @@ -0,0 +1,987 @@ +#!/usr/bin/python3 -i +# +# Copyright (c) 2015-2016 The Khronos Group Inc. +# Copyright (c) 2015-2016 Valve Corporation +# Copyright (c) 2015-2016 LunarG, Inc. +# Copyright (c) 2015-2016 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: Dustin Graves + +import os,re,sys +import xml.etree.ElementTree as etree +from generator import * +from collections import namedtuple + + +# ParamCheckerGeneratorOptions - subclass of GeneratorOptions. +# +# Adds options used by ParamCheckerOutputGenerator object during Parameter +# validation layer generation. +# +# Additional members +# prefixText - list of strings to prefix generated header with +# (usually a copyright statement + calling convention macros). +# protectFile - True if multiple inclusion protection should be +# generated (based on the filename) around the entire header. +# protectFeature - True if #ifndef..#endif protection should be +# generated around a feature interface in the header file. +# genFuncPointers - True if function pointer typedefs should be +# generated +# protectProto - If conditional protection should be generated +# around prototype declarations, set to either '#ifdef' +# to require opt-in (#ifdef protectProtoStr) or '#ifndef' +# to require opt-out (#ifndef protectProtoStr). Otherwise +# set to None. +# protectProtoStr - #ifdef/#ifndef symbol to use around prototype +# declarations, if protectProto is set +# apicall - string to use for the function declaration prefix, +# such as APICALL on Windows. +# apientry - string to use for the calling convention macro, +# in typedefs, such as APIENTRY. +# apientryp - string to use for the calling convention macro +# in function pointer typedefs, such as APIENTRYP. +# indentFuncProto - True if prototype declarations should put each +# parameter on a separate line +# indentFuncPointer - True if typedefed function pointers should put each +# parameter on a separate line +# alignFuncParam - if nonzero and parameters are being put on a +# separate line, align parameter names at the specified column +class ParamCheckerGeneratorOptions(GeneratorOptions): + def __init__(self, + filename = None, + directory = '.', + apiname = None, + profile = None, + versions = '.*', + emitversions = '.*', + defaultExtensions = None, + addExtensions = None, + removeExtensions = None, + sortProcedure = regSortFeatures, + prefixText = "", + genFuncPointers = True, + protectFile = True, + protectFeature = True, + protectProto = None, + protectProtoStr = None, + apicall = '', + apientry = '', + apientryp = '', + indentFuncProto = True, + indentFuncPointer = False, + alignFuncParam = 0): + GeneratorOptions.__init__(self, filename, directory, apiname, profile, + versions, emitversions, defaultExtensions, + addExtensions, removeExtensions, sortProcedure) + self.prefixText = prefixText + self.genFuncPointers = genFuncPointers + self.protectFile = protectFile + self.protectFeature = protectFeature + self.protectProto = protectProto + self.protectProtoStr = protectProtoStr + self.apicall = apicall + self.apientry = apientry + self.apientryp = apientryp + self.indentFuncProto = indentFuncProto + self.indentFuncPointer = indentFuncPointer + self.alignFuncParam = alignFuncParam + +# ParamCheckerOutputGenerator - subclass of OutputGenerator. +# Generates param checker layer code. +# +# ---- methods ---- +# ParamCheckerOutputGenerator(errFile, warnFile, diagFile) - args as for +# OutputGenerator. Defines additional internal state. +# ---- methods overriding base class ---- +# beginFile(genOpts) +# endFile() +# beginFeature(interface, emit) +# endFeature() +# genType(typeinfo,name) +# genStruct(typeinfo,name) +# genGroup(groupinfo,name) +# genEnum(enuminfo, name) +# genCmd(cmdinfo) +class ParamCheckerOutputGenerator(OutputGenerator): + """Generate ParamChecker code based on XML element attributes""" + # This is an ordered list of sections in the header file. + ALL_SECTIONS = ['command'] + def __init__(self, + errFile = sys.stderr, + warnFile = sys.stderr, + diagFile = sys.stdout): + OutputGenerator.__init__(self, errFile, warnFile, diagFile) + self.INDENT_SPACES = 4 + # Commands to ignore + self.blacklist = [ + 'vkGetInstanceProcAddr', + 'vkGetDeviceProcAddr', + 'vkEnumerateInstanceLayerProperties', + 'vkEnumerateInstanceExtensionsProperties', + 'vkEnumerateDeviceLayerProperties', + 'vkEnumerateDeviceExtensionsProperties', + 'vkCreateDebugReportCallbackEXT', + 'vkDebugReportMessageEXT'] + # Validation conditions for some special case struct members that are conditionally validated + self.structMemberValidationConditions = { 'VkPipelineColorBlendStateCreateInfo' : { 'logicOp' : '{}logicOpEnable == VK_TRUE' } } + # Header version + self.headerVersion = None + # Internal state - accumulators for different inner block text + self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) + self.structNames = [] # List of Vulkan struct typenames + self.stypes = [] # Values from the VkStructureType enumeration + self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType + self.handleTypes = set() # Set of handle type names + self.commands = [] # List of CommandData records for all Vulkan commands + self.structMembers = [] # List of StructMemberData records for all Vulkan structs + self.validatedStructs = dict() # Map of structs type names to generated validation code for that struct type + self.enumRanges = dict() # Map of enum name to BEGIN/END range values + self.flags = set() # Map of flags typenames + self.flagBits = dict() # Map of flag bits typename to list of values + # Named tuples to store struct and command data + self.StructType = namedtuple('StructType', ['name', 'value']) + self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isstaticarray', 'isbool', 'israngedenum', + 'isconst', 'isoptional', 'iscount', 'noautovalidity', 'len', 'extstructs', + 'condition', 'cdecl']) + self.CommandData = namedtuple('CommandData', ['name', 'params', 'cdecl']) + self.StructMemberData = namedtuple('StructMemberData', ['name', 'members']) + # + def incIndent(self, indent): + inc = ' ' * self.INDENT_SPACES + if indent: + return indent + inc + return inc + # + def decIndent(self, indent): + if indent and (len(indent) > self.INDENT_SPACES): + return indent[:-self.INDENT_SPACES] + return '' + # + def beginFile(self, genOpts): + OutputGenerator.beginFile(self, genOpts) + # C-specific + # + # User-supplied prefix text, if any (list of strings) + if (genOpts.prefixText): + for s in genOpts.prefixText: + write(s, file=self.outFile) + # + # Multiple inclusion protection & C++ wrappers. + if (genOpts.protectFile and self.genOpts.filename): + headerSym = re.sub('\.h', '_H', os.path.basename(self.genOpts.filename)).upper() + write('#ifndef', headerSym, file=self.outFile) + write('#define', headerSym, '1', file=self.outFile) + self.newline() + # + # Headers + write('#include ', file=self.outFile) + self.newline() + write('#include "vulkan/vulkan.h"', file=self.outFile) + write('#include "vk_layer_extension_utils.h"', file=self.outFile) + write('#include "parameter_validation_utils.h"', file=self.outFile) + # + # Macros + self.newline() + write('#ifndef UNUSED_PARAMETER', file=self.outFile) + write('#define UNUSED_PARAMETER(x) (void)(x)', file=self.outFile) + write('#endif // UNUSED_PARAMETER', file=self.outFile) + # + # Namespace + self.newline() + write('namespace parameter_validation {', file = self.outFile) + def endFile(self): + # C-specific + self.newline() + # Namespace + write('} // namespace parameter_validation', file = self.outFile) + # Finish C++ wrapper and multiple inclusion protection + if (self.genOpts.protectFile and self.genOpts.filename): + self.newline() + write('#endif', file=self.outFile) + # Finish processing in superclass + OutputGenerator.endFile(self) + def beginFeature(self, interface, emit): + # Start processing in superclass + OutputGenerator.beginFeature(self, interface, emit) + # C-specific + # Accumulate includes, defines, types, enums, function pointer typedefs, + # end function prototypes separately for this feature. They're only + # printed in endFeature(). + self.headerVersion = None + self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) + self.structNames = [] + self.stypes = [] + self.structTypes = dict() + self.handleTypes = set() + self.commands = [] + self.structMembers = [] + self.validatedStructs = dict() + self.enumRanges = dict() + self.flags = set() + self.flagBits = dict() + def endFeature(self): + # C-specific + # Actually write the interface to the output file. + if (self.emit): + self.newline() + # If type declarations are needed by other features based on + # this one, it may be necessary to suppress the ExtraProtect, + # or move it below the 'for section...' loop. + if (self.featureExtraProtect != None): + write('#ifdef', self.featureExtraProtect, file=self.outFile) + # Generate the struct member checking code from the captured data + self.processStructMemberData() + # Generate the command parameter checking code from the captured data + self.processCmdData() + # Write the declaration for the HeaderVersion + if self.headerVersion: + write('const uint32_t GeneratedHeaderVersion = {};'.format(self.headerVersion), file=self.outFile) + self.newline() + # Write the declarations for the VkFlags values combining all flag bits + for flag in sorted(self.flags): + flagBits = flag.replace('Flags', 'FlagBits') + if flagBits in self.flagBits: + bits = self.flagBits[flagBits] + decl = 'const {} All{} = {}'.format(flag, flagBits, bits[0]) + for bit in bits[1:]: + decl += '|' + bit + decl += ';' + write(decl, file=self.outFile) + self.newline() + # Write the parameter validation code to the file + if (self.sections['command']): + if (self.genOpts.protectProto): + write(self.genOpts.protectProto, + self.genOpts.protectProtoStr, file=self.outFile) + write('\n'.join(self.sections['command']), end='', file=self.outFile) + if (self.featureExtraProtect != None): + write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) + else: + self.newline() + # Finish processing in superclass + OutputGenerator.endFeature(self) + # + # Append a definition to the specified section + def appendSection(self, section, text): + # self.sections[section].append('SECTION: ' + section + '\n') + self.sections[section].append(text) + # + # Type generation + def genType(self, typeinfo, name): + OutputGenerator.genType(self, typeinfo, name) + typeElem = typeinfo.elem + # If the type is a struct type, traverse the imbedded tags + # generating a structure. Otherwise, emit the tag text. + category = typeElem.get('category') + if (category == 'struct' or category == 'union'): + self.structNames.append(name) + self.genStruct(typeinfo, name) + elif (category == 'handle'): + self.handleTypes.add(name) + elif (category == 'bitmask'): + self.flags.add(name) + elif (category == 'define'): + if name == 'VK_HEADER_VERSION': + nameElem = typeElem.find('name') + self.headerVersion = noneStr(nameElem.tail).strip() + # + # Struct parameter check generation. + # This is a special case of the tag where the contents are + # interpreted as a set of tags instead of freeform C + # C type declarations. The tags are just like + # tags - they are a declaration of a struct or union member. + # Only simple member declarations are supported (no nested + # structs etc.) + def genStruct(self, typeinfo, typeName): + OutputGenerator.genStruct(self, typeinfo, typeName) + conditions = self.structMemberValidationConditions[typeName] if typeName in self.structMemberValidationConditions else None + 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] + stypeValue = '' + cdecl = self.makeCParamDecl(member, 0) + # 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 + # Check for parameter name in lens set + iscount = False + if name in lens: + iscount = True + # The pNext members are not tagged as optional, but are treated as + # optional for parameter NULL checks. Static array members + # are also treated as optional to skip NULL pointer validation, as + # they won't be NULL. + isstaticarray = self.paramIsStaticArray(member) + isoptional = False + if self.paramIsOptional(member) or (name == 'pNext') or (isstaticarray): + isoptional = True + membersInfo.append(self.CommandParam(type=type, name=name, + ispointer=self.paramIsPointer(member), + isstaticarray=isstaticarray, + isbool=True if type == 'VkBool32' else False, + israngedenum=True if type in self.enumRanges else False, + isconst=True if 'const' in cdecl else False, + isoptional=isoptional, + iscount=iscount, + noautovalidity=True if member.attrib.get('noautovalidity') is not None else False, + len=self.getLen(member), + extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None, + condition=conditions[name] if conditions and name in conditions else None, + cdecl=cdecl)) + self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo)) + # + # Capture group (e.g. C "enum" type) info to be used for + # param check code generation. + # These are concatenated together with other types. + def genGroup(self, groupinfo, groupName): + OutputGenerator.genGroup(self, groupinfo, groupName) + groupElem = groupinfo.elem + # + # Store the sType values + if groupName == 'VkStructureType': + for elem in groupElem.findall('enum'): + self.stypes.append(elem.get('name')) + elif 'FlagBits' in groupName: + bits = [] + for elem in groupElem.findall('enum'): + bits.append(elem.get('name')) + if bits: + self.flagBits[groupName] = bits + else: + # Determine if begin/end ranges are needed (we don't do this for VkStructureType, which has a more finely grained check) + expandName = re.sub(r'([0-9a-z_])([A-Z0-9][^A-Z0-9]?)',r'\1_\2',groupName).upper() + expandPrefix = expandName + expandSuffix = '' + expandSuffixMatch = re.search(r'[A-Z][A-Z]+$',groupName) + if expandSuffixMatch: + expandSuffix = '_' + expandSuffixMatch.group() + # Strip off the suffix from the prefix + expandPrefix = expandName.rsplit(expandSuffix, 1)[0] + isEnum = ('FLAG_BITS' not in expandPrefix) + if isEnum: + self.enumRanges[groupName] = (expandPrefix + '_BEGIN_RANGE' + expandSuffix, expandPrefix + '_END_RANGE' + expandSuffix) + # + # Capture command parameter info to be used for param + # check code generation. + def genCmd(self, cmdinfo, name): + OutputGenerator.genCmd(self, cmdinfo, name) + if name not in self.blacklist: + params = cmdinfo.elem.findall('param') + # Get list of array lengths + lens = set() + for param in params: + len = self.getLen(param) + if len: + lens.add(len) + # Get param info + paramsInfo = [] + for param in params: + paramInfo = self.getTypeNameTuple(param) + cdecl = self.makeCParamDecl(param, 0) + # Check for parameter name in lens set + iscount = False + if paramInfo[1] in lens: + iscount = True + paramsInfo.append(self.CommandParam(type=paramInfo[0], name=paramInfo[1], + ispointer=self.paramIsPointer(param), + isstaticarray=self.paramIsStaticArray(param), + isbool=True if paramInfo[0] == 'VkBool32' else False, + israngedenum=True if paramInfo[0] in self.enumRanges else False, + isconst=True if 'const' in cdecl else False, + isoptional=self.paramIsOptional(param), + iscount=iscount, + noautovalidity=True if param.attrib.get('noautovalidity') is not None else False, + len=self.getLen(param), + extstructs=None, + condition=None, + cdecl=cdecl)) + self.commands.append(self.CommandData(name=name, params=paramsInfo, cdecl=self.makeCDecls(cmdinfo.elem)[0])) + # + # Check if the parameter passed in is a pointer + def paramIsPointer(self, param): + ispointer = 0 + paramtype = param.find('type') + if (paramtype.tail is not None) and ('*' in paramtype.tail): + ispointer = paramtype.tail.count('*') + elif paramtype.text[:4] == 'PFN_': + # Treat function pointer typedefs as a pointer to a single value + ispointer = 1 + 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 + # + # Check if the parameter passed in is optional + # Returns a list of Boolean values for comma separated len attributes (len='false,true') + def paramIsOptional(self, param): + # See if the handle is optional + isoptional = False + # Simple, if it's optional, return true + optString = param.attrib.get('optional') + if optString: + if optString == 'true': + isoptional = True + elif ',' in optString: + opts = [] + for opt in optString.split(','): + val = opt.strip() + if val == 'true': + opts.append(True) + elif val == 'false': + opts.append(False) + else: + print('Unrecognized len attribute value',val) + isoptional = opts + return isoptional + # + # Check if the handle passed in is optional + # Uses the same logic as ValidityOutputGenerator.isHandleOptional + def isHandleOptional(self, param, lenParam): + # Simple, if it's optional, return true + if param.isoptional: + return True + # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes. + if param.noautovalidity: + return True + # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional + if lenParam and lenParam.isoptional: + return True + return False + # + # 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) + # + # Get the cached VkStructureType value for the specified struct typename, or generate a VkStructureType + # value assuming the struct is defined by a different feature + def getStructType(self, typename): + value = None + if typename in self.structTypes: + value = self.structTypes[typename].value + else: + value = self.genVkStructureType(typename) + self.logMsg('diag', 'ParameterValidation: Generating {} for {} structure type that was not defined by the current feature'.format(value, typename)) + return value + # + # 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 + result = str(result).replace('::', '->') + return result + # + # 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) + # + # Find a named parameter in a parameter list + def getParamByName(self, params, name): + for param in params: + if param.name == name: + return param + return None + # + # Extract length values from latexmath. Currently an inflexible solution that looks for specific + # patterns that are found in vk.xml. Will need to be updated when new patterns are introduced. + def parseLateXMath(self, source): + name = 'ERROR' + decoratedName = 'ERROR' + if 'mathit' in source: + # Matches expressions similar to 'latexmath:[$\lceil{\mathit{rasterizationSamples} \over 32}\rceil$]' + match = re.match(r'latexmath\s*\:\s*\[\s*\$\\l(\w+)\s*\{\s*\\mathit\s*\{\s*(\w+)\s*\}\s*\\over\s*(\d+)\s*\}\s*\\r(\w+)\$\s*\]', source) + if not match or match.group(1) != match.group(4): + raise 'Unrecognized latexmath expression' + name = match.group(2) + decoratedName = '{}({}/{})'.format(*match.group(1, 2, 3)) + else: + # Matches expressions similar to 'latexmath : [$dataSize \over 4$]' + match = re.match(r'latexmath\s*\:\s*\[\s*\$\s*(\w+)\s*\\over\s*(\d+)\s*\$\s*\]', source) + name = match.group(1) + decoratedName = '{}/{}'.format(*match.group(1, 2)) + return name, decoratedName + # + # Get the length paramater record for the specified parameter name + def getLenParam(self, params, name): + lenParam = None + if name: + if '->' in name: + # The count is obtained by dereferencing a member of a struct parameter + lenParam = self.CommandParam(name=name, iscount=True, ispointer=False, isbool=False, israngedenum=False, isconst=False, + isstaticarray=None, isoptional=False, type=None, noautovalidity=False, len=None, extstructs=None, + condition=None, cdecl=None) + elif 'latexmath' in name: + lenName, decoratedName = self.parseLateXMath(name) + lenParam = self.getParamByName(params, lenName) + # TODO: Zero-check the result produced by the equation? + # Copy the stored len parameter entry and overwrite the name with the processed latexmath equation + #param = self.getParamByName(params, lenName) + #lenParam = self.CommandParam(name=decoratedName, iscount=param.iscount, ispointer=param.ispointer, + # isoptional=param.isoptional, type=param.type, len=param.len, + # isstaticarray=param.isstaticarray, extstructs=param.extstructs, + # noautovalidity=True, condition=None, cdecl=param.cdecl) + else: + lenParam = self.getParamByName(params, name) + return lenParam + # + # Convert a vulkan.h command declaration into a parameter_validation.h definition + def getCmdDef(self, cmd): + # + # Strip the trailing ';' and split into individual lines + lines = cmd.cdecl[:-1].split('\n') + # Replace Vulkan prototype + lines[0] = 'static bool parameter_validation_' + cmd.name + '(' + # Replace the first argument with debug_report_data, when the first + # argument is a handle (not vkCreateInstance) + reportData = ' debug_report_data*'.ljust(self.genOpts.alignFuncParam) + 'report_data,' + if cmd.name != 'vkCreateInstance': + lines[1] = reportData + else: + lines.insert(1, reportData) + return '\n'.join(lines) + # + # Generate the code to check for a NULL dereference before calling the + # validation function + def genCheckedLengthCall(self, name, exprs): + count = name.count('->') + if count: + checkedExpr = [] + localIndent = '' + elements = name.split('->') + # Open the if expression blocks + for i in range(0, count): + checkedExpr.append(localIndent + 'if ({} != NULL) {{\n'.format('->'.join(elements[0:i+1]))) + localIndent = self.incIndent(localIndent) + # Add the validation expression + for expr in exprs: + checkedExpr.append(localIndent + expr) + # Close the if blocks + for i in range(0, count): + localIndent = self.decIndent(localIndent) + checkedExpr.append(localIndent + '}\n') + return [checkedExpr] + # No if statements were required + return exprs + # + # Generate code to check for a specific condition before executing validation code + def genConditionalCall(self, prefix, condition, exprs): + checkedExpr = [] + localIndent = '' + formattedCondition = condition.format(prefix) + checkedExpr.append(localIndent + 'if ({})\n'.format(formattedCondition)) + checkedExpr.append(localIndent + '{\n') + localIndent = self.incIndent(localIndent) + for expr in exprs: + checkedExpr.append(localIndent + expr) + localIndent = self.decIndent(localIndent) + checkedExpr.append(localIndent + '}\n') + return [checkedExpr] + # + # Generate the sType check string + def makeStructTypeCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, lenPtrRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec): + checkExpr = [] + stype = self.structTypes[value.type] + if lenValue: + # This is an array with a pointer to a count value + if lenValue.ispointer: + # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required + checkExpr.append('skipCall |= validate_struct_type_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {}, {});\n'.format( + funcPrintName, lenPtrRequired, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, sv=stype.value, pf=prefix, **postProcSpec)) + # This is an array with an integer count value + else: + checkExpr.append('skipCall |= validate_struct_type_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {});\n'.format( + funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, sv=stype.value, pf=prefix, **postProcSpec)) + # This is an individual struct + else: + checkExpr.append('skipCall |= validate_struct_type(report_data, "{}", {ppp}"{}"{pps}, "{sv}", {}{vn}, {sv}, {});\n'.format( + funcPrintName, valuePrintName, prefix, valueRequired, vn=value.name, sv=stype.value, **postProcSpec)) + return checkExpr + # + # Generate the handle check string + def makeHandleCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec): + checkExpr = [] + if lenValue: + if lenValue.ispointer: + # This is assumed to be an output array with a pointer to a count value + raise('Unsupported parameter validation case: Output handle array elements are not NULL checked') + else: + # This is an array with an integer count value + checkExpr.append('skipCall |= validate_handle_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format( + funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec)) + else: + # This is assumed to be an output handle pointer + raise('Unsupported parameter validation case: Output handles are not NULL checked') + return checkExpr + # + # Generate check string for an array of VkFlags values + def makeFlagsArrayCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec): + checkExpr = [] + flagBitsName = value.type.replace('Flags', 'FlagBits') + if not flagBitsName in self.flagBits: + raise('Unsupported parameter validation case: array of reserved VkFlags') + else: + allFlags = 'All' + flagBitsName + checkExpr.append('skipCall |= validate_flags_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, "{}", {}, {pf}{}, {pf}{}, {}, {});\n'.format(funcPrintName, lenPrintName, valuePrintName, flagBitsName, allFlags, lenValue.name, value.name, lenValueRequired, valueRequired, pf=prefix, **postProcSpec)) + return checkExpr + # + # Generate pNext check string + def makeStructNextCheck(self, prefix, value, funcPrintName, valuePrintName, postProcSpec): + checkExpr = [] + # Generate an array of acceptable VkStructureType values for pNext + extStructCount = 0 + extStructVar = 'NULL' + extStructNames = 'NULL' + if value.extstructs: + structs = value.extstructs.split(',') + checkExpr.append('const VkStructureType allowedStructs[] = {' + ', '.join([self.getStructType(s) for s in structs]) + '};\n') + extStructCount = 'ARRAY_SIZE(allowedStructs)' + extStructVar = 'allowedStructs' + extStructNames = '"' + ', '.join(structs) + '"' + checkExpr.append('skipCall |= validate_struct_pnext(report_data, "{}", {ppp}"{}"{pps}, {}, {}{}, {}, {}, GeneratedHeaderVersion);\n'.format( + funcPrintName, valuePrintName, extStructNames, prefix, value.name, extStructCount, extStructVar, **postProcSpec)) + return checkExpr + # + # Generate the pointer check string + def makePointerCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, lenPtrRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec): + checkExpr = [] + if lenValue: + # This is an array with a pointer to a count value + if lenValue.ispointer: + # If count and array parameters are optional, there will be no validation + if valueRequired == 'true' or lenPtrRequired == 'true' or lenValueRequired == 'true': + # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required + checkExpr.append('skipCall |= validate_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {}, {});\n'.format( + funcPrintName, lenPtrRequired, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec)) + # This is an array with an integer count value + else: + # If count and array parameters are optional, there will be no validation + if valueRequired == 'true' or lenValueRequired == 'true': + # Arrays of strings receive special processing + validationFuncName = 'validate_array' if value.type != 'char' else 'validate_string_array' + checkExpr.append('skipCall |= {}(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format( + validationFuncName, funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec)) + if checkExpr: + if lenValue and ('->' in lenValue.name): + # Add checks to ensure the validation call does not dereference a NULL pointer to obtain the count + checkExpr = self.genCheckedLengthCall(lenValue.name, checkExpr) + # This is an individual struct that is not allowed to be NULL + elif not value.isoptional: + # Function pointers need a reinterpret_cast to void* + if value.type[:4] == 'PFN_': + checkExpr.append('skipCall |= validate_required_pointer(report_data, "{}", {ppp}"{}"{pps}, reinterpret_cast({}{}));\n'.format(funcPrintName, valuePrintName, prefix, value.name, **postProcSpec)) + else: + checkExpr.append('skipCall |= validate_required_pointer(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcPrintName, valuePrintName, prefix, value.name, **postProcSpec)) + return checkExpr + # + # Process struct member validation code, performing name suibstitution if required + def processStructMemberCode(self, line, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec): + # Build format specifier list + kwargs = {} + if '{postProcPrefix}' in line: + # If we have a tuple that includes a format string and format parameters, need to use ParameterName class + if type(memberDisplayNamePrefix) is tuple: + kwargs['postProcPrefix'] = 'ParameterName(' + else: + kwargs['postProcPrefix'] = postProcSpec['ppp'] + if '{postProcSuffix}' in line: + # If we have a tuple that includes a format string and format parameters, need to use ParameterName class + if type(memberDisplayNamePrefix) is tuple: + kwargs['postProcSuffix'] = ', ParameterName::IndexVector{{ {}{} }})'.format(postProcSpec['ppi'], memberDisplayNamePrefix[1]) + else: + kwargs['postProcSuffix'] = postProcSpec['pps'] + if '{postProcInsert}' in line: + # If we have a tuple that includes a format string and format parameters, need to use ParameterName class + if type(memberDisplayNamePrefix) is tuple: + kwargs['postProcInsert'] = '{}{}, '.format(postProcSpec['ppi'], memberDisplayNamePrefix[1]) + else: + kwargs['postProcInsert'] = postProcSpec['ppi'] + if '{funcName}' in line: + kwargs['funcName'] = funcName + if '{valuePrefix}' in line: + kwargs['valuePrefix'] = memberNamePrefix + if '{displayNamePrefix}' in line: + # Check for a tuple that includes a format string and format parameters to be used with the ParameterName class + if type(memberDisplayNamePrefix) is tuple: + kwargs['displayNamePrefix'] = memberDisplayNamePrefix[0] + else: + kwargs['displayNamePrefix'] = memberDisplayNamePrefix + + if kwargs: + # Need to escape the C++ curly braces + if 'IndexVector' in line: + line = line.replace('IndexVector{ ', 'IndexVector{{ ') + line = line.replace(' }),', ' }}),') + return line.format(**kwargs) + return line + # + # Process struct validation code for inclusion in function or parent struct validation code + def expandStructCode(self, lines, funcName, memberNamePrefix, memberDisplayNamePrefix, indent, output, postProcSpec): + for line in lines: + if output: + output[-1] += '\n' + if type(line) is list: + for sub in line: + output.append(self.processStructMemberCode(indent + sub, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec)) + else: + output.append(self.processStructMemberCode(indent + line, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec)) + return output + # + # Process struct pointer/array validation code, perfoeming name substitution if required + def expandStructPointerCode(self, prefix, value, lenValue, funcName, valueDisplayName, postProcSpec): + expr = [] + expr.append('if ({}{} != NULL)\n'.format(prefix, value.name)) + expr.append('{') + indent = self.incIndent(None) + if lenValue: + # Need to process all elements in the array + indexName = lenValue.name.replace('Count', 'Index') + expr[-1] += '\n' + expr.append(indent + 'for (uint32_t {iname} = 0; {iname} < {}{}; ++{iname})\n'.format(prefix, lenValue.name, iname=indexName)) + expr.append(indent + '{') + indent = self.incIndent(indent) + # Prefix for value name to display in error message + memberNamePrefix = '{}{}[{}].'.format(prefix, value.name, indexName) + memberDisplayNamePrefix = ('{}[%i].'.format(valueDisplayName), indexName) + else: + memberNamePrefix = '{}{}->'.format(prefix, value.name) + memberDisplayNamePrefix = '{}->'.format(valueDisplayName) + # + # Expand the struct validation lines + expr = self.expandStructCode(self.validatedStructs[value.type], funcName, memberNamePrefix, memberDisplayNamePrefix, indent, expr, postProcSpec) + # + if lenValue: + # Close if and for scopes + indent = self.decIndent(indent) + expr.append(indent + '}\n') + expr.append('}\n') + return expr + # + # Generate the parameter checking code + def genFuncBody(self, funcName, values, valuePrefix, displayNamePrefix, structTypeName): + lines = [] # Generated lines of code + unused = [] # Unused variable names + for value in values: + usedLines = [] + lenParam = None + # + # Prefix and suffix for post processing of parameter names for struct members. Arrays of structures need special processing to include the array index in the full parameter name. + postProcSpec = {} + postProcSpec['ppp'] = '' if not structTypeName else '{postProcPrefix}' + postProcSpec['pps'] = '' if not structTypeName else '{postProcSuffix}' + postProcSpec['ppi'] = '' if not structTypeName else '{postProcInsert}' + # + # Generate the full name of the value, which will be printed in the error message, by adding the variable prefix to the value name + valueDisplayName = '{}{}'.format(displayNamePrefix, value.name) + # + # Check for NULL pointers, ignore the inout count parameters that + # will be validated with their associated array + if (value.ispointer or value.isstaticarray) and not value.iscount: + # + # Parameters for function argument generation + req = 'true' # Paramerter cannot be NULL + cpReq = 'true' # Count pointer cannot be NULL + cvReq = 'true' # Count value cannot be 0 + lenDisplayName = None # Name of length parameter to print with validation messages; parameter name with prefix applied + # + # Generate required/optional parameter strings for the pointer and count values + if value.isoptional: + req = 'false' + if value.len: + # The parameter is an array with an explicit count parameter + lenParam = self.getLenParam(values, value.len) + lenDisplayName = '{}{}'.format(displayNamePrefix, lenParam.name) + if lenParam.ispointer: + # Count parameters that are pointers are inout + if type(lenParam.isoptional) is list: + if lenParam.isoptional[0]: + cpReq = 'false' + if lenParam.isoptional[1]: + cvReq = 'false' + else: + if lenParam.isoptional: + cpReq = 'false' + else: + if lenParam.isoptional: + cvReq = 'false' + # + # The parameter will not be processes when tagged as 'noautovalidity' + # For the pointer to struct case, the struct pointer will not be validated, but any + # members not tagged as 'noatuvalidity' will be validated + if value.noautovalidity: + # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually + self.logMsg('diag', 'ParameterValidation: No validation for {} {}'.format(structTypeName if structTypeName else funcName, value.name)) + else: + # + # If this is a pointer to a struct with an sType field, verify the type + if value.type in self.structTypes: + usedLines += self.makeStructTypeCheck(valuePrefix, value, lenParam, req, cvReq, cpReq, funcName, lenDisplayName, valueDisplayName, postProcSpec) + # If this is an input handle array that is not allowed to contain NULL handles, verify that none of the handles are VK_NULL_HANDLE + elif value.type in self.handleTypes and value.isconst and not self.isHandleOptional(value, lenParam): + usedLines += self.makeHandleCheck(valuePrefix, value, lenParam, req, cvReq, funcName, lenDisplayName, valueDisplayName, postProcSpec) + elif value.type in self.flags and value.isconst: + usedLines += self.makeFlagsArrayCheck(valuePrefix, value, lenParam, req, cvReq, funcName, lenDisplayName, valueDisplayName, postProcSpec) + elif value.isbool and value.isconst: + usedLines.append('skipCall |= validate_bool32_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, {pf}{}, {pf}{}, {}, {});\n'.format(funcName, lenDisplayName, valueDisplayName, lenParam.name, value.name, cvReq, req, pf=valuePrefix, **postProcSpec)) + elif value.israngedenum and value.isconst: + enumRange = self.enumRanges[value.type] + usedLines.append('skipCall |= validate_ranged_enum_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, "{}", {}, {}, {pf}{}, {pf}{}, {}, {});\n'.format(funcName, lenDisplayName, valueDisplayName, value.type, enumRange[0], enumRange[1], lenParam.name, value.name, cvReq, req, pf=valuePrefix, **postProcSpec)) + elif value.name == 'pNext': + # We need to ignore VkDeviceCreateInfo and VkInstanceCreateInfo, as the loader manipulates them in a way that is not documented in vk.xml + if not structTypeName in ['VkDeviceCreateInfo', 'VkInstanceCreateInfo']: + usedLines += self.makeStructNextCheck(valuePrefix, value, funcName, valueDisplayName, postProcSpec) + else: + usedLines += self.makePointerCheck(valuePrefix, value, lenParam, req, cvReq, cpReq, funcName, lenDisplayName, valueDisplayName, postProcSpec) + # + # If this is a pointer to a struct (input), see if it contains members that need to be checked + if value.type in self.validatedStructs and value.isconst: + usedLines.append(self.expandStructPointerCode(valuePrefix, value, lenParam, funcName, valueDisplayName, postProcSpec)) + # Non-pointer types + else: + # + # The parameter will not be processes when tagged as 'noautovalidity' + # For the struct case, the struct type will not be validated, but any + # members not tagged as 'noatuvalidity' will be validated + if value.noautovalidity: + # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually + self.logMsg('diag', 'ParameterValidation: No validation for {} {}'.format(structTypeName if structTypeName else funcName, value.name)) + else: + if value.type in self.structTypes: + stype = self.structTypes[value.type] + usedLines.append('skipCall |= validate_struct_type(report_data, "{}", {ppp}"{}"{pps}, "{sv}", &({}{vn}), {sv}, false);\n'.format( + funcName, valueDisplayName, valuePrefix, vn=value.name, sv=stype.value, **postProcSpec)) + elif value.type in self.handleTypes: + if not self.isHandleOptional(value, None): + usedLines.append('skipCall |= validate_required_handle(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcName, valueDisplayName, valuePrefix, value.name, **postProcSpec)) + elif value.type in self.flags: + flagBitsName = value.type.replace('Flags', 'FlagBits') + if not flagBitsName in self.flagBits: + usedLines.append('skipCall |= validate_reserved_flags(report_data, "{}", {ppp}"{}"{pps}, {pf}{});\n'.format(funcName, valueDisplayName, value.name, pf=valuePrefix, **postProcSpec)) + else: + flagsRequired = 'false' if value.isoptional else 'true' + allFlagsName = 'All' + flagBitsName + usedLines.append('skipCall |= validate_flags(report_data, "{}", {ppp}"{}"{pps}, "{}", {}, {pf}{}, {});\n'.format(funcName, valueDisplayName, flagBitsName, allFlagsName, value.name, flagsRequired, pf=valuePrefix, **postProcSpec)) + elif value.isbool: + usedLines.append('skipCall |= validate_bool32(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcName, valueDisplayName, valuePrefix, value.name, **postProcSpec)) + elif value.israngedenum: + enumRange = self.enumRanges[value.type] + usedLines.append('skipCall |= validate_ranged_enum(report_data, "{}", {ppp}"{}"{pps}, "{}", {}, {}, {}{});\n'.format(funcName, valueDisplayName, value.type, enumRange[0], enumRange[1], valuePrefix, value.name, **postProcSpec)) + # + # If this is a struct, see if it contains members that need to be checked + if value.type in self.validatedStructs: + memberNamePrefix = '{}{}.'.format(valuePrefix, value.name) + memberDisplayNamePrefix = '{}.'.format(valueDisplayName) + usedLines.append(self.expandStructCode(self.validatedStructs[value.type], funcName, memberNamePrefix, memberDisplayNamePrefix, '', [], postProcSpec)) + # + # Append the parameter check to the function body for the current command + if usedLines: + # Apply special conditional checks + if value.condition: + usedLines = self.genConditionalCall(valuePrefix, value.condition, usedLines) + lines += usedLines + elif not value.iscount: + # If no expression was generated for this value, it is unreferenced by the validation function, unless + # it is an array count, which is indirectly referenced for array valiadation. + unused.append(value.name) + return lines, unused + # + # Generate the struct member check code from the captured data + def processStructMemberData(self): + indent = self.incIndent(None) + for struct in self.structMembers: + # + # The string returned by genFuncBody will be nested in an if check for a NULL pointer, so needs its indent incremented + lines, unused = self.genFuncBody('{funcName}', struct.members, '{valuePrefix}', '{displayNamePrefix}', struct.name) + if lines: + self.validatedStructs[struct.name] = lines + # + # Generate the command param check code from the captured data + def processCmdData(self): + indent = self.incIndent(None) + for command in self.commands: + # Skip first parameter if it is a dispatch handle (everything except vkCreateInstance) + startIndex = 0 if command.name == 'vkCreateInstance' else 1 + lines, unused = self.genFuncBody(command.name, command.params[startIndex:], '', '', None) + if lines: + cmdDef = self.getCmdDef(command) + '\n' + cmdDef += '{\n' + # Process unused parameters, Ignoring the first dispatch handle parameter, which is not + # processed by parameter_validation (except for vkCreateInstance, which does not have a + # handle as its first parameter) + if unused: + for name in unused: + cmdDef += indent + 'UNUSED_PARAMETER({});\n'.format(name) + if len(unused) > 0: + cmdDef += '\n' + cmdDef += indent + 'bool skipCall = false;\n' + for line in lines: + cmdDef += '\n' + if type(line) is list: + for sub in line: + cmdDef += indent + sub + else: + cmdDef += indent + line + cmdDef += '\n' + cmdDef += indent + 'return skipCall;\n' + cmdDef += '}\n' + self.appendSection('command', cmdDef) diff --git a/scripts/reg.py b/scripts/reg.py new file mode 100755 index 00000000..98436a33 --- /dev/null +++ b/scripts/reg.py @@ -0,0 +1,813 @@ +#!/usr/bin/python3 -i +# +# Copyright (c) 2013-2016 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 io,os,re,string,sys,copy +import xml.etree.ElementTree as etree + +# matchAPIProfile - returns whether an API and profile +# being generated matches an element's profile +# api - string naming the API to match +# profile - string naming the profile to match +# elem - Element which (may) have 'api' and 'profile' +# attributes to match to. +# If a tag is not present in the Element, the corresponding API +# or profile always matches. +# Otherwise, the tag must exactly match the API or profile. +# Thus, if 'profile' = core: +# with no attribute will match +# will match +# will not match +# Possible match conditions: +# Requested Element +# Profile Profile +# --------- -------- +# None None Always matches +# 'string' None Always matches +# None 'string' Does not match. Can't generate multiple APIs +# or profiles, so if an API/profile constraint +# is present, it must be asked for explicitly. +# 'string' 'string' Strings must match +# +# ** In the future, we will allow regexes for the attributes, +# not just strings, so that api="^(gl|gles2)" will match. Even +# this isn't really quite enough, we might prefer something +# like "gl(core)|gles1(common-lite)". +def matchAPIProfile(api, profile, elem): + """Match a requested API & profile name to a api & profile attributes of an Element""" + match = True + # Match 'api', if present + if ('api' in elem.attrib): + if (api == None): + raise UserWarning("No API requested, but 'api' attribute is present with value '" + + elem.get('api') + "'") + elif (api != elem.get('api')): + # Requested API doesn't match attribute + return False + if ('profile' in elem.attrib): + if (profile == None): + raise UserWarning("No profile requested, but 'profile' attribute is present with value '" + + elem.get('profile') + "'") + elif (profile != elem.get('profile')): + # Requested profile doesn't match attribute + return False + return True + +# BaseInfo - base class for information about a registry feature +# (type/group/enum/command/API/extension). +# required - should this feature be defined during header generation +# (has it been removed by a profile or version)? +# declared - has this feature been defined already? +# elem - etree Element for this feature +# resetState() - reset required/declared to initial values. Used +# prior to generating a new API interface. +class BaseInfo: + """Represents the state of a registry feature, used during API generation""" + def __init__(self, elem): + self.required = False + self.declared = False + self.elem = elem + def resetState(self): + self.required = False + self.declared = False + +# TypeInfo - registry information about a type. No additional state +# beyond BaseInfo is required. +class TypeInfo(BaseInfo): + """Represents the state of a registry type""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.additionalValidity = [] + self.removedValidity = [] + def resetState(self): + BaseInfo.resetState(self) + self.additionalValidity = [] + self.removedValidity = [] + +# GroupInfo - registry information about a group of related enums +# in an block, generally corresponding to a C "enum" type. +class GroupInfo(BaseInfo): + """Represents the state of a registry group""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + +# EnumInfo - registry information about an enum +# type - numeric type of the value of the tag +# ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 ) +class EnumInfo(BaseInfo): + """Represents the state of a registry enum""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.type = elem.get('type') + if (self.type == None): + self.type = '' + +# CmdInfo - registry information about a command +class CmdInfo(BaseInfo): + """Represents the state of a registry command""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.additionalValidity = [] + self.removedValidity = [] + def resetState(self): + BaseInfo.resetState(self) + self.additionalValidity = [] + self.removedValidity = [] + +# FeatureInfo - registry information about an API +# or +# name - feature name string (e.g. 'VK_KHR_surface') +# version - feature version number (e.g. 1.2). +# features are unversioned and assigned version number 0. +# ** This is confusingly taken from the 'number' attribute of . +# Needs fixing. +# number - extension number, used for ordering and for +# assigning enumerant offsets. features do +# not have extension numbers and are assigned number 0. +# category - category, e.g. VERSION or khr/vendor tag +# emit - has this feature been defined already? +class FeatureInfo(BaseInfo): + """Represents the state of an API feature (version/extension)""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.name = elem.get('name') + # Determine element category (vendor). Only works + # for elements. + if (elem.tag == 'feature'): + self.category = 'VERSION' + self.version = elem.get('number') + self.number = "0" + self.supported = None + else: + self.category = self.name.split('_', 2)[1] + self.version = "0" + self.number = elem.get('number') + self.supported = elem.get('supported') + self.emit = False + +from generator import write, GeneratorOptions, OutputGenerator + +# Registry - object representing an API registry, loaded from an XML file +# Members +# tree - ElementTree containing the root +# typedict - dictionary of TypeInfo objects keyed by type name +# groupdict - dictionary of GroupInfo objects keyed by group name +# enumdict - dictionary of EnumInfo objects keyed by enum name +# cmddict - dictionary of CmdInfo objects keyed by command name +# apidict - dictionary of Elements keyed by API name +# extensions - list of Elements +# extdict - dictionary of Elements keyed by extension name +# gen - OutputGenerator object used to write headers / messages +# genOpts - GeneratorOptions object used to control which +# fetures to write and how to format them +# emitFeatures - True to actually emit features for a version / extension, +# or False to just treat them as emitted +# Public methods +# loadElementTree(etree) - load registry from specified ElementTree +# loadFile(filename) - load registry from XML file +# setGenerator(gen) - OutputGenerator to use +# parseTree() - parse the registry once loaded & create dictionaries +# dumpReg(maxlen, filehandle) - diagnostic to dump the dictionaries +# to specified file handle (default stdout). Truncates type / +# enum / command elements to maxlen characters (default 80) +# generator(g) - specify the output generator object +# apiGen(apiname, genOpts) - generate API headers for the API type +# and profile specified in genOpts, but only for the versions and +# extensions specified there. +# apiReset() - call between calls to apiGen() to reset internal state +# Private methods +# addElementInfo(elem,info,infoName,dictionary) - add feature info to dict +# lookupElementInfo(fname,dictionary) - lookup feature info in dict +class Registry: + """Represents an API registry loaded from XML""" + def __init__(self): + self.tree = None + self.typedict = {} + self.groupdict = {} + self.enumdict = {} + self.cmddict = {} + self.apidict = {} + self.extensions = [] + self.extdict = {} + # A default output generator, so commands prior to apiGen can report + # errors via the generator object. + self.gen = OutputGenerator() + self.genOpts = None + self.emitFeatures = False + def loadElementTree(self, tree): + """Load ElementTree into a Registry object and parse it""" + self.tree = tree + self.parseTree() + def loadFile(self, file): + """Load an API registry XML file into a Registry object and parse it""" + self.tree = etree.parse(file) + self.parseTree() + def setGenerator(self, gen): + """Specify output generator object. None restores the default generator""" + self.gen = gen + self.gen.setRegistry(self) + + # addElementInfo - add information about an element to the + # corresponding dictionary + # elem - ///// Element + # info - corresponding {Type|Group|Enum|Cmd|Feature}Info object + # infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' + # dictionary - self.{type|group|enum|cmd|api|ext}dict + # If the Element has an 'api' attribute, the dictionary key is the + # tuple (name,api). If not, the key is the name. 'name' is an + # attribute of the Element + def addElementInfo(self, elem, info, infoName, dictionary): + if ('api' in elem.attrib): + key = (elem.get('name'),elem.get('api')) + else: + key = elem.get('name') + if key in dictionary: + self.gen.logMsg('warn', '*** Attempt to redefine', + infoName, 'with key:', key) + else: + dictionary[key] = info + # + # lookupElementInfo - find a {Type|Enum|Cmd}Info object by name. + # If an object qualified by API name exists, use that. + # fname - name of type / enum / command + # dictionary - self.{type|enum|cmd}dict + def lookupElementInfo(self, fname, dictionary): + key = (fname, self.genOpts.apiname) + if (key in dictionary): + # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) + return dictionary[key] + elif (fname in dictionary): + # self.gen.logMsg('diag', 'Found generic element for feature', fname) + return dictionary[fname] + else: + return None + def parseTree(self): + """Parse the registry Element, once created""" + # This must be the Element for the root + self.reg = self.tree.getroot() + # + # Create dictionary of registry types from toplevel tags + # and add 'name' attribute to each tag (where missing) + # based on its element. + # + # There's usually one block; more are OK + # Required attributes: 'name' or nested tag contents + self.typedict = {} + for type in self.reg.findall('types/type'): + # If the doesn't already have a 'name' attribute, set + # it from contents of its tag. + if (type.get('name') == None): + type.attrib['name'] = type.find('name').text + self.addElementInfo(type, TypeInfo(type), 'type', self.typedict) + # + # Create dictionary of registry enum groups from tags. + # + # Required attributes: 'name'. If no name is given, one is + # generated, but that group can't be identified and turned into an + # enum type definition - it's just a container for tags. + self.groupdict = {} + for group in self.reg.findall('enums'): + self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) + # + # Create dictionary of registry enums from tags + # + # tags usually define different namespaces for the values + # defined in those tags, but the actual names all share the + # same dictionary. + # Required attributes: 'name', 'value' + # For containing which have type="enum" or type="bitmask", + # tag all contained s are required. This is a stopgap until + # a better scheme for tagging core and extension enums is created. + self.enumdict = {} + for enums in self.reg.findall('enums'): + required = (enums.get('type') != None) + for enum in enums.findall('enum'): + enumInfo = EnumInfo(enum) + enumInfo.required = required + self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) + # + # Create dictionary of registry commands from tags + # and add 'name' attribute to each tag (where missing) + # based on its element. + # + # There's usually only one block; more are OK. + # Required attributes: 'name' or tag contents + self.cmddict = {} + for cmd in self.reg.findall('commands/command'): + # If the doesn't already have a 'name' attribute, set + # it from contents of its tag. + if (cmd.get('name') == None): + cmd.attrib['name'] = cmd.find('proto/name').text + ci = CmdInfo(cmd) + self.addElementInfo(cmd, ci, 'command', self.cmddict) + # + # Create dictionaries of API and extension interfaces + # from toplevel and tags. + # + self.apidict = {} + for feature in self.reg.findall('feature'): + featureInfo = FeatureInfo(feature) + self.addElementInfo(feature, featureInfo, 'feature', self.apidict) + self.extensions = self.reg.findall('extensions/extension') + self.extdict = {} + for feature in self.extensions: + featureInfo = FeatureInfo(feature) + self.addElementInfo(feature, featureInfo, 'extension', self.extdict) + + # Add additional enums defined only in tags + # to the corresponding core type. + # When seen here, the element, processed to contain the + # numeric enum value, is added to the corresponding + # element, as well as adding to the enum dictionary. It is + # *removed* from the element it is introduced in. + # Not doing this will cause spurious genEnum() + # calls to be made in output generation, and it's easier + # to handle here than in genEnum(). + # + # In lxml.etree, an Element can have only one parent, so the + # append() operation also removes the element. But in Python's + # ElementTree package, an Element can have multiple parents. So + # it must be explicitly removed from the tag, leading + # to the nested loop traversal of / elements + # below. + # + # This code also adds a 'extnumber' attribute containing the + # extension number, used for enumerant value calculation. + # + # For tags which are actually just constants, if there's + # no 'extends' tag but there is a 'value' or 'bitpos' tag, just + # add an EnumInfo record to the dictionary. That works because + # output generation of constants is purely dependency-based, and + # doesn't need to iterate through the XML tags. + # + # Something like this will need to be done for 'feature's up + # above, if we use the same mechanism for adding to the core + # API in 1.1. + # + for elem in feature.findall('require'): + for enum in elem.findall('enum'): + addEnumInfo = False + groupName = enum.get('extends') + if (groupName != None): + # self.gen.logMsg('diag', '*** Found extension enum', + # enum.get('name')) + # Add extension number attribute to the element + enum.attrib['extnumber'] = featureInfo.number + enum.attrib['extname'] = featureInfo.name + enum.attrib['supported'] = featureInfo.supported + # Look up the GroupInfo with matching groupName + if (groupName in self.groupdict.keys()): + # self.gen.logMsg('diag', '*** Matching group', + # groupName, 'found, adding element...') + gi = self.groupdict[groupName] + gi.elem.append(enum) + # Remove element from parent tag + # This should be a no-op in lxml.etree + elem.remove(enum) + else: + self.gen.logMsg('warn', '*** NO matching group', + groupName, 'for enum', enum.get('name'), 'found.') + addEnumInfo = True + elif (enum.get('value') or enum.get('bitpos')): + # self.gen.logMsg('diag', '*** Adding extension constant "enum"', + # enum.get('name')) + addEnumInfo = True + if (addEnumInfo): + enumInfo = EnumInfo(enum) + self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) + def dumpReg(self, maxlen = 40, filehandle = sys.stdout): + """Dump all the dictionaries constructed from the Registry object""" + write('***************************************', file=filehandle) + write(' ** Dumping Registry contents **', file=filehandle) + write('***************************************', file=filehandle) + write('// Types', file=filehandle) + for name in self.typedict: + tobj = self.typedict[name] + write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) + write('// Groups', file=filehandle) + for name in self.groupdict: + gobj = self.groupdict[name] + write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) + write('// Enums', file=filehandle) + for name in self.enumdict: + eobj = self.enumdict[name] + write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) + write('// Commands', file=filehandle) + for name in self.cmddict: + cobj = self.cmddict[name] + write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) + write('// APIs', file=filehandle) + for key in self.apidict: + write(' API Version ', key, '->', + etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) + write('// Extensions', file=filehandle) + for key in self.extdict: + write(' Extension', key, '->', + etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) + # write('***************************************', file=filehandle) + # write(' ** Dumping XML ElementTree **', file=filehandle) + # write('***************************************', file=filehandle) + # write(etree.tostring(self.tree.getroot(),pretty_print=True), file=filehandle) + # + # typename - name of type + # required - boolean (to tag features as required or not) + def markTypeRequired(self, typename, required): + """Require (along with its dependencies) or remove (but not its dependencies) a type""" + self.gen.logMsg('diag', '*** tagging type:', typename, '-> required =', required) + # Get TypeInfo object for tag corresponding to typename + type = self.lookupElementInfo(typename, self.typedict) + if (type != None): + if (required): + # Tag type dependencies in 'required' attributes as + # required. This DOES NOT un-tag dependencies in a + # tag. See comments in markRequired() below for the reason. + if ('requires' in type.elem.attrib): + depType = type.elem.get('requires') + self.gen.logMsg('diag', '*** Generating dependent type', + depType, 'for type', typename) + self.markTypeRequired(depType, required) + # Tag types used in defining this type (e.g. in nested + # tags) + # Look for in entire tree, + # not just immediate children + for subtype in type.elem.findall('.//type'): + self.gen.logMsg('diag', '*** markRequired: type requires dependent ', subtype.text) + self.markTypeRequired(subtype.text, required) + # Tag enums used in defining this type, for example in + # member[MEMBER_SIZE] + for subenum in type.elem.findall('.//enum'): + self.gen.logMsg('diag', '*** markRequired: type requires dependent ', subenum.text) + self.markEnumRequired(subenum.text, required) + type.required = required + else: + self.gen.logMsg('warn', '*** type:', typename , 'IS NOT DEFINED') + # + # enumname - name of enum + # required - boolean (to tag features as required or not) + def markEnumRequired(self, enumname, required): + self.gen.logMsg('diag', '*** tagging enum:', enumname, '-> required =', required) + enum = self.lookupElementInfo(enumname, self.enumdict) + if (enum != None): + enum.required = required + else: + self.gen.logMsg('warn', '*** enum:', enumname , 'IS NOT DEFINED') + # + # features - Element for or tag + # required - boolean (to tag features as required or not) + def markRequired(self, features, required): + """Require or remove features specified in the Element""" + self.gen.logMsg('diag', '*** markRequired (features = , required =', required, ')') + # Loop over types, enums, and commands in the tag + # @@ It would be possible to respect 'api' and 'profile' attributes + # in individual features, but that's not done yet. + for typeElem in features.findall('type'): + self.markTypeRequired(typeElem.get('name'), required) + for enumElem in features.findall('enum'): + self.markEnumRequired(enumElem.get('name'), required) + for cmdElem in features.findall('command'): + name = cmdElem.get('name') + self.gen.logMsg('diag', '*** tagging command:', name, '-> required =', required) + cmd = self.lookupElementInfo(name, self.cmddict) + if (cmd != None): + cmd.required = required + # Tag all parameter types of this command as required. + # This DOES NOT remove types of commands in a + # tag, because many other commands may use the same type. + # We could be more clever and reference count types, + # instead of using a boolean. + if (required): + # Look for in entire tree, + # not just immediate children + for type in cmd.elem.findall('.//type'): + self.gen.logMsg('diag', '*** markRequired: command implicitly requires dependent type', type.text) + self.markTypeRequired(type.text, required) + else: + self.gen.logMsg('warn', '*** command:', name, 'IS NOT DEFINED') + # + # interface - Element for or , containing + # and tags + # api - string specifying API name being generated + # profile - string specifying API profile being generated + def requireAndRemoveFeatures(self, interface, api, profile): + """Process and tags for a or """ + # marks things that are required by this version/profile + for feature in interface.findall('require'): + if (matchAPIProfile(api, profile, feature)): + self.markRequired(feature,True) + # marks things that are removed by this version/profile + for feature in interface.findall('remove'): + if (matchAPIProfile(api, profile, feature)): + self.markRequired(feature,False) + + def assignAdditionalValidity(self, interface, api, profile): + # + # Loop over all usage inside all tags. + for feature in interface.findall('require'): + if (matchAPIProfile(api, profile, feature)): + for v in feature.findall('usage'): + if v.get('command'): + self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v)) + if v.get('struct'): + self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v)) + + # + # Loop over all usage inside all tags. + for feature in interface.findall('remove'): + if (matchAPIProfile(api, profile, feature)): + for v in feature.findall('usage'): + if v.get('command'): + self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v)) + if v.get('struct'): + self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v)) + + # + # generateFeature - generate a single type / enum group / enum / command, + # and all its dependencies as needed. + # fname - name of feature (//) + # ftype - type of feature, 'type' | 'enum' | 'command' + # dictionary - of *Info objects - self.{type|enum|cmd}dict + def generateFeature(self, fname, ftype, dictionary): + f = self.lookupElementInfo(fname, dictionary) + if (f == None): + # No such feature. This is an error, but reported earlier + self.gen.logMsg('diag', '*** No entry found for feature', fname, + 'returning!') + return + # + # If feature isn't required, or has already been declared, return + if (not f.required): + self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(not required)') + return + if (f.declared): + self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(already declared)') + return + # Always mark feature declared, as though actually emitted + f.declared = True + # + # Pull in dependent declaration(s) of the feature. + # For types, there may be one type in the 'required' attribute of + # the element, as well as many in imbedded and tags + # within the element. + # For commands, there may be many in tags within the element. + # For enums, no dependencies are allowed (though perhaps if you + # have a uint64 enum, it should require that type). + genProc = None + if (ftype == 'type'): + genProc = self.gen.genType + if ('requires' in f.elem.attrib): + depname = f.elem.get('requires') + self.gen.logMsg('diag', '*** Generating required dependent type', + depname) + self.generateFeature(depname, 'type', self.typedict) + for subtype in f.elem.findall('.//type'): + self.gen.logMsg('diag', '*** Generating required dependent ', + subtype.text) + self.generateFeature(subtype.text, 'type', self.typedict) + for subtype in f.elem.findall('.//enum'): + self.gen.logMsg('diag', '*** Generating required dependent ', + subtype.text) + self.generateFeature(subtype.text, 'enum', self.enumdict) + # If the type is an enum group, look up the corresponding + # group in the group dictionary and generate that instead. + if (f.elem.get('category') == 'enum'): + self.gen.logMsg('diag', '*** Type', fname, 'is an enum group, so generate that instead') + group = self.lookupElementInfo(fname, self.groupdict) + if (group == None): + # Unless this is tested for, it's probably fatal to call below + genProc = None + self.logMsg('warn', '*** NO MATCHING ENUM GROUP FOUND!!!') + else: + genProc = self.gen.genGroup + f = group + elif (ftype == 'command'): + genProc = self.gen.genCmd + for type in f.elem.findall('.//type'): + depname = type.text + self.gen.logMsg('diag', '*** Generating required parameter type', + depname) + self.generateFeature(depname, 'type', self.typedict) + elif (ftype == 'enum'): + genProc = self.gen.genEnum + # Actually generate the type only if emitting declarations + if self.emitFeatures: + self.gen.logMsg('diag', '*** Emitting', ftype, 'decl for', fname) + genProc(f, fname) + else: + self.gen.logMsg('diag', '*** Skipping', ftype, fname, + '(not emitting this feature)') + # + # generateRequiredInterface - generate all interfaces required + # by an API version or extension + # interface - Element for or + def generateRequiredInterface(self, interface): + """Generate required C interface for specified API version/extension""" + + # + # Loop over all features inside all tags. + for features in interface.findall('require'): + for t in features.findall('type'): + self.generateFeature(t.get('name'), 'type', self.typedict) + for e in features.findall('enum'): + self.generateFeature(e.get('name'), 'enum', self.enumdict) + for c in features.findall('command'): + self.generateFeature(c.get('name'), 'command', self.cmddict) + + # + # apiGen(genOpts) - generate interface for specified versions + # genOpts - GeneratorOptions object with parameters used + # by the Generator object. + def apiGen(self, genOpts): + """Generate interfaces for the specified API type and range of versions""" + # + self.gen.logMsg('diag', '*******************************************') + self.gen.logMsg('diag', ' Registry.apiGen file:', genOpts.filename, + 'api:', genOpts.apiname, + 'profile:', genOpts.profile) + self.gen.logMsg('diag', '*******************************************') + # + self.genOpts = genOpts + # Reset required/declared flags for all features + self.apiReset() + # + # Compile regexps used to select versions & extensions + regVersions = re.compile(self.genOpts.versions) + regEmitVersions = re.compile(self.genOpts.emitversions) + regAddExtensions = re.compile(self.genOpts.addExtensions) + regRemoveExtensions = re.compile(self.genOpts.removeExtensions) + # + # Get all matching API versions & add to list of FeatureInfo + features = [] + apiMatch = False + for key in self.apidict: + fi = self.apidict[key] + api = fi.elem.get('api') + if (api == self.genOpts.apiname): + apiMatch = True + if (regVersions.match(fi.version)): + # Matches API & version #s being generated. Mark for + # emission and add to the features[] list . + # @@ Could use 'declared' instead of 'emit'? + fi.emit = (regEmitVersions.match(fi.version) != None) + features.append(fi) + if (not fi.emit): + self.gen.logMsg('diag', '*** NOT tagging feature api =', api, + 'name =', fi.name, 'version =', fi.version, + 'for emission (does not match emitversions pattern)') + else: + self.gen.logMsg('diag', '*** NOT including feature api =', api, + 'name =', fi.name, 'version =', fi.version, + '(does not match requested versions)') + else: + self.gen.logMsg('diag', '*** NOT including feature api =', api, + 'name =', fi.name, + '(does not match requested API)') + if (not apiMatch): + self.gen.logMsg('warn', '*** No matching API versions found!') + # + # Get all matching extensions, in order by their extension number, + # and add to the list of features. + # Start with extensions tagged with 'api' pattern matching the API + # being generated. Add extensions matching the pattern specified in + # regExtensions, then remove extensions matching the pattern + # specified in regRemoveExtensions + for (extName,ei) in sorted(self.extdict.items(),key = lambda x : x[1].number): + extName = ei.name + include = False + # + # Include extension if defaultExtensions is not None and if the + # 'supported' attribute matches defaultExtensions. The regexp in + # 'supported' must exactly match defaultExtensions, so bracket + # it with ^(pat)$. + pat = '^(' + ei.elem.get('supported') + ')$' + if (self.genOpts.defaultExtensions and + re.match(pat, self.genOpts.defaultExtensions)): + self.gen.logMsg('diag', '*** Including extension', + extName, "(defaultExtensions matches the 'supported' attribute)") + include = True + # + # Include additional extensions if the extension name matches + # the regexp specified in the generator options. This allows + # forcing extensions into an interface even if they're not + # tagged appropriately in the registry. + if (regAddExtensions.match(extName) != None): + self.gen.logMsg('diag', '*** Including extension', + extName, '(matches explicitly requested extensions to add)') + include = True + # Remove extensions if the name matches the regexp specified + # in generator options. This allows forcing removal of + # extensions from an interface even if they're tagged that + # way in the registry. + if (regRemoveExtensions.match(extName) != None): + self.gen.logMsg('diag', '*** Removing extension', + extName, '(matches explicitly requested extensions to remove)') + include = False + # + # If the extension is to be included, add it to the + # extension features list. + if (include): + ei.emit = True + features.append(ei) + else: + self.gen.logMsg('diag', '*** NOT including extension', + extName, '(does not match api attribute or explicitly requested extensions)') + # + # Sort the extension features list, if a sort procedure is defined + if (self.genOpts.sortProcedure): + self.genOpts.sortProcedure(features) + # + # Pass 1: loop over requested API versions and extensions tagging + # types/commands/features as required (in an block) or no + # longer required (in an block). It is possible to remove + # a feature in one version and restore it later by requiring it in + # a later version. + # If a profile other than 'None' is being generated, it must + # match the profile attribute (if any) of the and + # tags. + self.gen.logMsg('diag', '*** PASS 1: TAG FEATURES ********************************************') + for f in features: + self.gen.logMsg('diag', '*** PASS 1: Tagging required and removed features for', + f.name) + self.requireAndRemoveFeatures(f.elem, self.genOpts.apiname, self.genOpts.profile) + self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile) + # + # Pass 2: loop over specified API versions and extensions printing + # declarations for required things which haven't already been + # generated. + self.gen.logMsg('diag', '*** PASS 2: GENERATE INTERFACES FOR FEATURES ************************') + self.gen.beginFile(self.genOpts) + for f in features: + self.gen.logMsg('diag', '*** PASS 2: Generating interface for', + f.name) + emit = self.emitFeatures = f.emit + if (not emit): + self.gen.logMsg('diag', '*** PASS 2: NOT declaring feature', + f.elem.get('name'), 'because it is not tagged for emission') + # Generate the interface (or just tag its elements as having been + # emitted, if they haven't been). + self.gen.beginFeature(f.elem, emit) + self.generateRequiredInterface(f.elem) + self.gen.endFeature() + self.gen.endFile() + # + # apiReset - use between apiGen() calls to reset internal state + # + def apiReset(self): + """Reset type/enum/command dictionaries before generating another API""" + for type in self.typedict: + self.typedict[type].resetState() + for enum in self.enumdict: + self.enumdict[enum].resetState() + for cmd in self.cmddict: + self.cmddict[cmd].resetState() + for cmd in self.apidict: + self.apidict[cmd].resetState() + # + # validateGroups - check that group= attributes match actual groups + # + def validateGroups(self): + """Validate group= attributes on and tags""" + # Keep track of group names not in tags + badGroup = {} + self.gen.logMsg('diag', '*** VALIDATING GROUP ATTRIBUTES ***') + for cmd in self.reg.findall('commands/command'): + proto = cmd.find('proto') + funcname = cmd.find('proto/name').text + if ('group' in proto.attrib.keys()): + group = proto.get('group') + # self.gen.logMsg('diag', '*** Command ', funcname, ' has return group ', group) + if (group not in self.groupdict.keys()): + # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group) + if (group not in badGroup.keys()): + badGroup[group] = 1 + else: + badGroup[group] = badGroup[group] + 1 + for param in cmd.findall('param'): + pname = param.find('name') + if (pname != None): + pname = pname.text + else: + pname = type.get('name') + if ('group' in param.attrib.keys()): + group = param.get('group') + if (group not in self.groupdict.keys()): + # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group) + if (group not in badGroup.keys()): + badGroup[group] = 1 + else: + badGroup[group] = badGroup[group] + 1 + if (len(badGroup.keys()) > 0): + self.gen.logMsg('diag', '*** SUMMARY OF UNRECOGNIZED GROUPS ***') + for key in sorted(badGroup.keys()): + self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times') diff --git a/scripts/threading_generator.py b/scripts/threading_generator.py new file mode 100644 index 00000000..0d0df12a --- /dev/null +++ b/scripts/threading_generator.py @@ -0,0 +1,467 @@ +#!/usr/bin/python3 -i +# +# Copyright (c) 2015-2016 The Khronos Group Inc. +# Copyright (c) 2015-2016 Valve Corporation +# Copyright (c) 2015-2016 LunarG, Inc. +# Copyright (c) 2015-2016 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: Mike Stroyan + +import os,re,sys +from generator import * + +# ThreadGeneratorOptions - subclass of GeneratorOptions. +# +# Adds options used by ThreadOutputGenerator objects during threading +# layer generation. +# +# Additional members +# prefixText - list of strings to prefix generated header with +# (usually a copyright statement + calling convention macros). +# protectFile - True if multiple inclusion protection should be +# generated (based on the filename) around the entire header. +# protectFeature - True if #ifndef..#endif protection should be +# generated around a feature interface in the header file. +# genFuncPointers - True if function pointer typedefs should be +# generated +# protectProto - If conditional protection should be generated +# around prototype declarations, set to either '#ifdef' +# to require opt-in (#ifdef protectProtoStr) or '#ifndef' +# to require opt-out (#ifndef protectProtoStr). Otherwise +# set to None. +# protectProtoStr - #ifdef/#ifndef symbol to use around prototype +# declarations, if protectProto is set +# apicall - string to use for the function declaration prefix, +# such as APICALL on Windows. +# apientry - string to use for the calling convention macro, +# in typedefs, such as APIENTRY. +# apientryp - string to use for the calling convention macro +# in function pointer typedefs, such as APIENTRYP. +# indentFuncProto - True if prototype declarations should put each +# parameter on a separate line +# indentFuncPointer - True if typedefed function pointers should put each +# parameter on a separate line +# alignFuncParam - if nonzero and parameters are being put on a +# separate line, align parameter names at the specified column +class ThreadGeneratorOptions(GeneratorOptions): + def __init__(self, + filename = None, + directory = '.', + apiname = None, + profile = None, + versions = '.*', + emitversions = '.*', + defaultExtensions = None, + addExtensions = None, + removeExtensions = None, + sortProcedure = regSortFeatures, + prefixText = "", + genFuncPointers = True, + protectFile = True, + protectFeature = True, + protectProto = None, + protectProtoStr = None, + apicall = '', + apientry = '', + apientryp = '', + indentFuncProto = True, + indentFuncPointer = False, + alignFuncParam = 0): + GeneratorOptions.__init__(self, filename, directory, apiname, profile, + versions, emitversions, defaultExtensions, + addExtensions, removeExtensions, sortProcedure) + self.prefixText = prefixText + self.genFuncPointers = genFuncPointers + self.protectFile = protectFile + self.protectFeature = protectFeature + self.protectProto = protectProto + self.protectProtoStr = protectProtoStr + self.apicall = apicall + self.apientry = apientry + self.apientryp = apientryp + self.indentFuncProto = indentFuncProto + self.indentFuncPointer = indentFuncPointer + self.alignFuncParam = alignFuncParam + +# ThreadOutputGenerator - subclass of OutputGenerator. +# Generates Thread checking framework +# +# ---- methods ---- +# ThreadOutputGenerator(errFile, warnFile, diagFile) - args as for +# OutputGenerator. Defines additional internal state. +# ---- methods overriding base class ---- +# beginFile(genOpts) +# endFile() +# beginFeature(interface, emit) +# endFeature() +# genType(typeinfo,name) +# genStruct(typeinfo,name) +# genGroup(groupinfo,name) +# genEnum(enuminfo, name) +# genCmd(cmdinfo) +class ThreadOutputGenerator(OutputGenerator): + """Generate specified API interfaces in a specific style, such as a C header""" + # This is an ordered list of sections in the header file. + TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', + 'group', 'bitmask', 'funcpointer', 'struct'] + ALL_SECTIONS = TYPE_SECTIONS + ['command'] + 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.sections = dict([(section, []) for section in self.ALL_SECTIONS]) + self.intercepts = [] + + # Check if the parameter passed in is a pointer to an array + def paramIsArray(self, param): + return param.attrib.get('len') is not None + + # Check if the parameter passed in is a pointer + def paramIsPointer(self, param): + ispointer = False + for elem in param: + #write('paramIsPointer '+elem.text, file=sys.stderr) + #write('elem.tag '+elem.tag, file=sys.stderr) + #if (elem.tail is None): + # write('elem.tail is None', file=sys.stderr) + #else: + # write('elem.tail '+elem.tail, file=sys.stderr) + if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail: + ispointer = True + # write('is pointer', file=sys.stderr) + return ispointer + def makeThreadUseBlock(self, cmd, functionprefix): + """Generate C function pointer typedef for Element""" + paramdecl = '' + thread_check_dispatchable_objects = [ + "VkCommandBuffer", + "VkDevice", + "VkInstance", + "VkQueue", + ] + thread_check_nondispatchable_objects = [ + "VkBuffer", + "VkBufferView", + "VkCommandPool", + "VkDescriptorPool", + "VkDescriptorSetLayout", + "VkDeviceMemory", + "VkEvent", + "VkFence", + "VkFramebuffer", + "VkImage", + "VkImageView", + "VkPipeline", + "VkPipelineCache", + "VkPipelineLayout", + "VkQueryPool", + "VkRenderPass", + "VkSampler", + "VkSemaphore", + "VkShaderModule", + ] + + # Find and add any parameters that are thread unsafe + params = cmd.findall('param') + for param in params: + paramname = param.find('name') + if False: # self.paramIsPointer(param): + paramdecl += ' // not watching use of pointer ' + paramname.text + '\n' + else: + externsync = param.attrib.get('externsync') + if externsync == 'true': + if self.paramIsArray(param): + paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n' + paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + '[index]);\n' + paramdecl += ' }\n' + else: + paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + ');\n' + elif (param.attrib.get('externsync')): + if self.paramIsArray(param): + # Externsync can list pointers to arrays of members to synchronize + paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n' + for member in externsync.split(","): + # Replace first empty [] in member name with index + element = member.replace('[]','[index]',1) + if '[]' in element: + # Replace any second empty [] in element name with + # inner array index based on mapping array names like + # "pSomeThings[]" to "someThingCount" array size. + # This could be more robust by mapping a param member + # name to a struct type and "len" attribute. + limit = element[0:element.find('s[]')] + 'Count' + dotp = limit.rfind('.p') + limit = limit[0:dotp+1] + limit[dotp+2:dotp+3].lower() + limit[dotp+3:] + paramdecl += ' for(uint32_t index2=0;index2<'+limit+';index2++)\n' + element = element.replace('[]','[index2]') + paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + element + ');\n' + paramdecl += ' }\n' + else: + # externsync can list members to synchronize + for member in externsync.split(","): + member = str(member).replace("::", "->") + paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + member + ');\n' + else: + paramtype = param.find('type') + if paramtype is not None: + paramtype = paramtype.text + else: + paramtype = 'None' + if paramtype in thread_check_dispatchable_objects or paramtype in thread_check_nondispatchable_objects: + if self.paramIsArray(param) and ('pPipelines' != paramname.text): + paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n' + paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + '[index]);\n' + paramdecl += ' }\n' + elif not self.paramIsPointer(param): + # Pointer params are often being created. + # They are not being read from. + paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + ');\n' + explicitexternsyncparams = cmd.findall("param[@externsync]") + if (explicitexternsyncparams is not None): + for param in explicitexternsyncparams: + externsyncattrib = param.attrib.get('externsync') + paramname = param.find('name') + paramdecl += ' // Host access to ' + if externsyncattrib == 'true': + if self.paramIsArray(param): + paramdecl += 'each member of ' + paramname.text + elif self.paramIsPointer(param): + paramdecl += 'the object referenced by ' + paramname.text + else: + paramdecl += paramname.text + else: + paramdecl += externsyncattrib + paramdecl += ' must be externally synchronized\n' + + # Find and add any "implicit" parameters that are thread unsafe + implicitexternsyncparams = cmd.find('implicitexternsyncparams') + if (implicitexternsyncparams is not None): + for elem in implicitexternsyncparams: + paramdecl += ' // ' + paramdecl += elem.text + paramdecl += ' must be externally synchronized between host accesses\n' + + if (paramdecl == ''): + return None + else: + return paramdecl + def beginFile(self, genOpts): + OutputGenerator.beginFile(self, genOpts) + # C-specific + # + # Multiple inclusion protection & C++ namespace. + if (genOpts.protectFile and self.genOpts.filename): + headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename)) + write('#ifndef', headerSym, file=self.outFile) + write('#define', headerSym, '1', file=self.outFile) + self.newline() + write('namespace threading {', file=self.outFile) + self.newline() + # + # User-supplied prefix text, if any (list of strings) + if (genOpts.prefixText): + for s in genOpts.prefixText: + write(s, file=self.outFile) + def endFile(self): + # C-specific + # Finish C++ namespace and multiple inclusion protection + self.newline() + # record intercepted procedures + write('// intercepts', file=self.outFile) + write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile) + write('\n'.join(self.intercepts), file=self.outFile) + write('};\n', file=self.outFile) + self.newline() + write('} // namespace threading', file=self.outFile) + if (self.genOpts.protectFile and self.genOpts.filename): + self.newline() + write('#endif', file=self.outFile) + # Finish processing in superclass + OutputGenerator.endFile(self) + def beginFeature(self, interface, emit): + #write('// starting beginFeature', file=self.outFile) + # Start processing in superclass + OutputGenerator.beginFeature(self, interface, emit) + # C-specific + # Accumulate includes, defines, types, enums, function pointer typedefs, + # end function prototypes separately for this feature. They're only + # printed in endFeature(). + self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) + #write('// ending beginFeature', file=self.outFile) + def endFeature(self): + # C-specific + # Actually write the interface to the output file. + #write('// starting endFeature', file=self.outFile) + if (self.emit): + self.newline() + if (self.genOpts.protectFeature): + write('#ifndef', self.featureName, file=self.outFile) + # If type declarations are needed by other features based on + # this one, it may be necessary to suppress the ExtraProtect, + # or move it below the 'for section...' loop. + #write('// endFeature looking at self.featureExtraProtect', file=self.outFile) + if (self.featureExtraProtect != None): + write('#ifdef', self.featureExtraProtect, file=self.outFile) + #write('#define', self.featureName, '1', file=self.outFile) + for section in self.TYPE_SECTIONS: + #write('// endFeature writing section'+section, file=self.outFile) + contents = self.sections[section] + if contents: + write('\n'.join(contents), file=self.outFile) + self.newline() + #write('// endFeature looking at self.sections[command]', file=self.outFile) + if (self.sections['command']): + write('\n'.join(self.sections['command']), end='', file=self.outFile) + self.newline() + if (self.featureExtraProtect != None): + write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) + if (self.genOpts.protectFeature): + write('#endif /*', self.featureName, '*/', file=self.outFile) + # Finish processing in superclass + OutputGenerator.endFeature(self) + #write('// ending endFeature', file=self.outFile) + # + # Append a definition to the specified section + def appendSection(self, section, text): + # self.sections[section].append('SECTION: ' + section + '\n') + self.sections[section].append(text) + # + # Type generation + def genType(self, typeinfo, name): + pass + # + # Struct (e.g. C "struct" type) generation. + # This is a special case of the tag where the contents are + # interpreted as a set of tags instead of freeform C + # C type declarations. The tags are just like + # tags - they are a declaration of a struct or union member. + # Only simple member declarations are supported (no nested + # structs etc.) + def genStruct(self, typeinfo, typeName): + OutputGenerator.genStruct(self, typeinfo, typeName) + body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n' + # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam) + for member in typeinfo.elem.findall('.//member'): + body += self.makeCParamDecl(member, self.genOpts.alignFuncParam) + body += ';\n' + body += '} ' + typeName + ';\n' + self.appendSection('struct', body) + # + # Group (e.g. C "enum" type) generation. + # These are concatenated together with other types. + def genGroup(self, groupinfo, groupName): + pass + # Enumerant generation + # tags may specify their values in several ways, but are usually + # just integers. + def genEnum(self, enuminfo, name): + pass + # + # Command generation + def genCmd(self, cmdinfo, name): + # Commands shadowed by interface functions and are not implemented + interface_functions = [ + 'vkEnumerateInstanceLayerProperties', + 'vkEnumerateInstanceExtensionProperties', + 'vkEnumerateDeviceLayerProperties', + ] + if name in interface_functions: + return + special_functions = [ + 'vkGetDeviceProcAddr', + 'vkGetInstanceProcAddr', + 'vkCreateDevice', + 'vkDestroyDevice', + 'vkCreateInstance', + 'vkDestroyInstance', + 'vkAllocateCommandBuffers', + 'vkFreeCommandBuffers', + 'vkCreateDebugReportCallbackEXT', + 'vkDestroyDebugReportCallbackEXT', + ] + if name in special_functions: + decls = self.makeCDecls(cmdinfo.elem) + self.appendSection('command', '') + self.appendSection('command', '// declare only') + self.appendSection('command', decls[0]) + self.intercepts += [ ' {"%s", reinterpret_cast(%s)},' % (name,name[2:]) ] + return + if "KHR" in name: + self.appendSection('command', '// TODO - not wrapping KHR function ' + name) + return + if ("DebugMarker" in name) and ("EXT" in name): + self.appendSection('command', '// TODO - not wrapping EXT function ' + name) + return + # Determine first if this function needs to be intercepted + startthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'start') + if startthreadsafety is None: + return + finishthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'finish') + # record that the function will be intercepted + if (self.featureExtraProtect != None): + self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ] + self.intercepts += [ ' {"%s", reinterpret_cast(%s)},' % (name,name[2:]) ] + if (self.featureExtraProtect != None): + self.intercepts += [ '#endif' ] + + OutputGenerator.genCmd(self, cmdinfo, name) + # + decls = self.makeCDecls(cmdinfo.elem) + self.appendSection('command', '') + self.appendSection('command', decls[0][:-1]) + self.appendSection('command', '{') + # setup common to call wrappers + # first parameter is always dispatchable + dispatchable_type = cmdinfo.elem.find('param/type').text + dispatchable_name = cmdinfo.elem.find('param/name').text + self.appendSection('command', ' dispatch_key key = get_dispatch_key('+dispatchable_name+');') + self.appendSection('command', ' layer_data *my_data = get_my_data_ptr(key, layer_data_map);') + if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]: + self.appendSection('command', ' VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table;') + else: + self.appendSection('command', ' VkLayerDispatchTable *pTable = my_data->device_dispatch_table;') + # Declare result variable, if any. + resulttype = cmdinfo.elem.find('proto/type') + if (resulttype != None and resulttype.text == 'void'): + resulttype = None + if (resulttype != None): + self.appendSection('command', ' ' + resulttype.text + ' result;') + assignresult = 'result = ' + else: + assignresult = '' + + self.appendSection('command', ' bool threadChecks = startMultiThread();') + self.appendSection('command', ' if (threadChecks) {') + self.appendSection('command', " "+"\n ".join(str(startthreadsafety).rstrip().split("\n"))) + self.appendSection('command', ' }') + params = cmdinfo.elem.findall('param/name') + paramstext = ','.join([str(param.text) for param in params]) + API = cmdinfo.elem.attrib.get('name').replace('vk','pTable->',1) + self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');') + self.appendSection('command', ' if (threadChecks) {') + self.appendSection('command', " "+"\n ".join(str(finishthreadsafety).rstrip().split("\n"))) + self.appendSection('command', ' } else {') + self.appendSection('command', ' finishMultiThread();') + self.appendSection('command', ' }') + # Return result variable, if any. + if (resulttype != None): + self.appendSection('command', ' return result;') + self.appendSection('command', '}') + # + # override makeProtoName to drop the "vk" prefix + def makeProtoName(self, name, tail): + return self.genOpts.apientry + name[2:] + tail diff --git a/scripts/unique_objects_generator.py b/scripts/unique_objects_generator.py new file mode 100644 index 00000000..cdd2808c --- /dev/null +++ b/scripts/unique_objects_generator.py @@ -0,0 +1,760 @@ +#!/usr/bin/python3 -i +# +# Copyright (c) 2015-2016 The Khronos Group Inc. +# Copyright (c) 2015-2016 Valve Corporation +# Copyright (c) 2015-2016 LunarG, Inc. +# Copyright (c) 2015-2016 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: Tobin Ehlis +# Author: Mark Lobodzinski + +import os,re,sys +import xml.etree.ElementTree as etree +from generator import * +from collections import namedtuple + +# UniqueObjectsGeneratorOptions - subclass of GeneratorOptions. +# +# Adds options used by UniqueObjectsOutputGenerator objects during +# unique objects layer generation. +# +# Additional members +# prefixText - list of strings to prefix generated header with +# (usually a copyright statement + calling convention macros). +# protectFile - True if multiple inclusion protection should be +# generated (based on the filename) around the entire header. +# protectFeature - True if #ifndef..#endif protection should be +# generated around a feature interface in the header file. +# genFuncPointers - True if function pointer typedefs should be +# generated +# protectProto - If conditional protection should be generated +# around prototype declarations, set to either '#ifdef' +# to require opt-in (#ifdef protectProtoStr) or '#ifndef' +# to require opt-out (#ifndef protectProtoStr). Otherwise +# set to None. +# protectProtoStr - #ifdef/#ifndef symbol to use around prototype +# declarations, if protectProto is set +# apicall - string to use for the function declaration prefix, +# such as APICALL on Windows. +# apientry - string to use for the calling convention macro, +# in typedefs, such as APIENTRY. +# apientryp - string to use for the calling convention macro +# in function pointer typedefs, such as APIENTRYP. +# indentFuncProto - True if prototype declarations should put each +# parameter on a separate line +# indentFuncPointer - True if typedefed function pointers should put each +# parameter on a separate line +# alignFuncParam - if nonzero and parameters are being put on a +# separate line, align parameter names at the specified column +class UniqueObjectsGeneratorOptions(GeneratorOptions): + def __init__(self, + filename = None, + directory = '.', + apiname = None, + profile = None, + versions = '.*', + emitversions = '.*', + defaultExtensions = None, + addExtensions = None, + removeExtensions = None, + sortProcedure = regSortFeatures, + prefixText = "", + genFuncPointers = True, + protectFile = True, + protectFeature = True, + protectProto = None, + protectProtoStr = None, + apicall = '', + apientry = '', + apientryp = '', + indentFuncProto = True, + indentFuncPointer = False, + alignFuncParam = 0): + GeneratorOptions.__init__(self, filename, directory, apiname, profile, + versions, emitversions, defaultExtensions, + addExtensions, removeExtensions, sortProcedure) + self.prefixText = prefixText + self.genFuncPointers = genFuncPointers + self.protectFile = protectFile + self.protectFeature = protectFeature + self.protectProto = protectProto + self.protectProtoStr = protectProtoStr + self.apicall = apicall + self.apientry = apientry + self.apientryp = apientryp + self.indentFuncProto = indentFuncProto + self.indentFuncPointer = indentFuncPointer + self.alignFuncParam = alignFuncParam + +# UniqueObjectsOutputGenerator - subclass of OutputGenerator. +# Generates unique objects layer non-dispatchable handle-wrapping code. +# +# ---- methods ---- +# UniqueObjectsOutputGenerator(errFile, warnFile, diagFile) - args as for OutputGenerator. Defines additional internal state. +# ---- methods overriding base class ---- +# beginFile(genOpts) +# endFile() +# beginFeature(interface, emit) +# endFeature() +# genCmd(cmdinfo) +# genStruct() +# genType() +class UniqueObjectsOutputGenerator(OutputGenerator): + """Generate UniqueObjects code based on XML element attributes""" + # This is an ordered list of sections in the header file. + ALL_SECTIONS = ['command'] + def __init__(self, + errFile = sys.stderr, + warnFile = sys.stderr, + diagFile = sys.stdout): + OutputGenerator.__init__(self, errFile, warnFile, diagFile) + self.INDENT_SPACES = 4 + # Commands to ignore + self.intercepts = [] + # Commands which are not autogenerated but still intercepted + self.no_autogen_list = [ + 'vkGetDeviceProcAddr', + 'vkGetInstanceProcAddr', + 'vkCreateInstance', + 'vkDestroyInstance', + 'vkCreateDevice', + 'vkDestroyDevice', + 'vkAllocateMemory', + 'vkCreateComputePipelines', + 'vkCreateGraphicsPipelines', + 'vkCreateSwapchainKHR', + 'vkGetSwapchainImagesKHR', + 'vkEnumerateInstanceLayerProperties', + 'vkEnumerateDeviceLayerProperties', + 'vkEnumerateInstanceExtensionProperties', + ] + # Commands shadowed by interface functions and are not implemented + self.interface_functions = [ + 'vkGetPhysicalDeviceDisplayPropertiesKHR', + 'vkGetPhysicalDeviceDisplayPlanePropertiesKHR', + 'vkGetDisplayPlaneSupportedDisplaysKHR', + 'vkGetDisplayModePropertiesKHR', + # DebugReport APIs are hooked, but handled separately in the source file + 'vkCreateDebugReportCallbackEXT', + 'vkDestroyDebugReportCallbackEXT', + 'vkDebugReportMessageEXT', + ] + self.headerVersion = None + # Internal state - accumulators for different inner block text + self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) + self.structNames = [] # List of Vulkan struct typenames + self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType + self.handleTypes = set() # Set of handle type names + self.commands = [] # List of CommandData records for all Vulkan commands + self.structMembers = [] # List of StructMemberData records for all Vulkan structs + self.flags = set() # Map of flags typenames + # Named tuples to store struct and command data + self.StructType = namedtuple('StructType', ['name', 'value']) + self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isconst', 'iscount', 'len', 'extstructs', 'cdecl', 'islocal', 'iscreate', 'isdestroy']) + self.CommandData = namedtuple('CommandData', ['name', 'return_type', 'params', 'cdecl']) + self.StructMemberData = namedtuple('StructMemberData', ['name', 'members']) + # + def incIndent(self, indent): + inc = ' ' * self.INDENT_SPACES + if indent: + return indent + inc + return inc + # + def decIndent(self, indent): + if indent and (len(indent) > self.INDENT_SPACES): + return indent[:-self.INDENT_SPACES] + return '' + # + # Override makeProtoName to drop the "vk" prefix + def makeProtoName(self, name, tail): + return self.genOpts.apientry + name[2:] + tail + # + # Check if the parameter passed in is a pointer to an array + def paramIsArray(self, param): + return param.attrib.get('len') is not None + # + def beginFile(self, genOpts): + OutputGenerator.beginFile(self, genOpts) + # User-supplied prefix text, if any (list of strings) + if (genOpts.prefixText): + for s in genOpts.prefixText: + write(s, file=self.outFile) + # Namespace + self.newline() + write('namespace unique_objects {', file = self.outFile) + # + def endFile(self): + self.newline() + # Record intercepted procedures + write('// intercepts', file=self.outFile) + write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile) + write('\n'.join(self.intercepts), file=self.outFile) + write('};\n', file=self.outFile) + self.newline() + write('} // namespace unique_objects', file=self.outFile) + # Finish processing in superclass + OutputGenerator.endFile(self) + # + def beginFeature(self, interface, emit): + # Start processing in superclass + OutputGenerator.beginFeature(self, interface, emit) + self.headerVersion = None + self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) + self.structNames = [] + self.structTypes = dict() + self.handleTypes = set() + self.commands = [] + self.structMembers = [] + self.cmdMembers = [] + self.flags = set() + self.StructMemberData = namedtuple('StructMemberData', ['name', 'members']) + self.CmdMemberData = namedtuple('CmdMemberData', ['name', 'members']) + # + def endFeature(self): + # Actually write the interface to the output file. + if (self.emit): + self.newline() + if (self.featureExtraProtect != None): + write('#ifdef', self.featureExtraProtect, file=self.outFile) + # Write the unique_objects code to the file + if (self.sections['command']): + if (self.genOpts.protectProto): + write(self.genOpts.protectProto, + self.genOpts.protectProtoStr, file=self.outFile) + write('\n'.join(self.sections['command']), end='', file=self.outFile) + if (self.featureExtraProtect != None): + write('\n#endif //', self.featureExtraProtect, file=self.outFile) + else: + self.newline() + # Finish processing in superclass + OutputGenerator.endFeature(self) + # + def genType(self, typeinfo, name): + OutputGenerator.genType(self, typeinfo, name) + typeElem = typeinfo.elem + # If the type is a struct type, traverse the imbedded tags generating a structure. + # Otherwise, emit the tag text. + category = typeElem.get('category') + if (category == 'struct' or category == 'union'): + self.structNames.append(name) + self.genStruct(typeinfo, name) + # + # Append a definition to the specified section + def appendSection(self, section, text): + # self.sections[section].append('SECTION: ' + section + '\n') + self.sections[section].append(text) + # + # Check if the parameter passed in is a pointer + def paramIsPointer(self, param): + ispointer = False + for elem in param: + if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail: + ispointer = True + return ispointer + # + # Get the category of a type + def getTypeCategory(self, typename): + types = self.registry.tree.findall("types/type") + for elem in types: + if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename: + return elem.attrib.get('category') + # + # Check if a parent object is dispatchable or not + def isHandleTypeNonDispatchable(self, handletype): + handle = self.registry.tree.find("types/type/[name='" + handletype + "'][@category='handle']") + if handle is not None and handle.find('type').text == 'VK_DEFINE_NON_DISPATCHABLE_HANDLE': + return True + else: + return False + # + # 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 + # Spec has now notation for len attributes, using :: instead of platform specific pointer symbol + result = str(result).replace('::', '->') + return result + # + # 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) + # + # Struct parameter check generation. + # This is a special case of the tag where the contents are interpreted as a set of + # tags instead of freeform C type declarations. The tags are just like + # tags - they are a declaration of a struct or union member. Only simple member + # declarations are supported (no nested structs etc.) + def genStruct(self, typeinfo, typeName): + OutputGenerator.genStruct(self, typeinfo, typeName) + 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, 0) + # 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 + membersInfo.append(self.CommandParam(type=type, + name=name, + ispointer=self.paramIsPointer(member), + isconst=True if 'const' in cdecl else False, + iscount=True if name in lens else False, + len=self.getLen(member), + extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None, + cdecl=cdecl, + islocal=False, + iscreate=False, + isdestroy=False)) + self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo)) + # + # Insert a lock_guard line + def lock_guard(self, indent): + return '%sstd::lock_guard lock(global_lock);\n' % indent + # + # Determine if a struct has an NDO as a member or an embedded member + def struct_contains_ndo(self, struct_item): + struct_member_dict = dict(self.structMembers) + struct_members = struct_member_dict[struct_item] + + for member in struct_members: + if self.isHandleTypeNonDispatchable(member.type): + return True + elif member.type in struct_member_dict: + if self.struct_contains_ndo(member.type) == True: + return True + return False + # + # Return list of struct members which contain, or which sub-structures contain + # an NDO in a given list of parameters or members + def getParmeterStructsWithNdos(self, item_list): + struct_list = set() + for item in item_list: + paramtype = item.find('type') + typecategory = self.getTypeCategory(paramtype.text) + if typecategory == 'struct': + if self.struct_contains_ndo(paramtype.text) == True: + struct_list.add(item) + return struct_list + # + # Return list of non-dispatchable objects from a given list of parameters or members + def getNdosInParameterList(self, item_list, create_func): + ndo_list = set() + if create_func == True: + member_list = item_list[0:-1] + else: + member_list = item_list + for item in member_list: + if self.isHandleTypeNonDispatchable(paramtype.text): + ndo_list.add(item) + return ndo_list + # + # Generate source for creating a non-dispatchable object + def generate_create_ndo_code(self, indent, proto, params, cmd_info): + create_ndo_code = '' + if True in [create_txt in proto.text for create_txt in ['Create', 'Allocate']]: + handle_type = params[-1].find('type') + if self.isHandleTypeNonDispatchable(handle_type.text): + # Check for special case where multiple handles are returned + ndo_array = False + if cmd_info[-1].len is not None: + ndo_array = True; + handle_name = params[-1].find('name') + create_ndo_code += '%sif (VK_SUCCESS == result) {\n' % (indent) + indent = self.incIndent(indent) + create_ndo_code += '%sstd::lock_guard lock(global_lock);\n' % (indent) + ndo_dest = '*%s' % handle_name.text + if ndo_array == True: + create_ndo_code += '%sfor (uint32_t index0 = 0; index0 < %s; index0++) {\n' % (indent, cmd_info[-1].len) + indent = self.incIndent(indent) + ndo_dest = '%s[index0]' % cmd_info[-1].name + create_ndo_code += '%suint64_t unique_id = global_unique_id++;\n' % (indent) + create_ndo_code += '%sdev_data->unique_id_mapping[unique_id] = reinterpret_cast(%s);\n' % (indent, ndo_dest) + create_ndo_code += '%s%s = reinterpret_cast<%s&>(unique_id);\n' % (indent, ndo_dest, handle_type.text) + if ndo_array == True: + indent = self.decIndent(indent) + create_ndo_code += '%s}\n' % indent + indent = self.decIndent(indent) + create_ndo_code += '%s}\n' % (indent) + return create_ndo_code + # + # Generate source for destroying a non-dispatchable object + def generate_destroy_ndo_code(self, indent, proto, cmd_info): + destroy_ndo_code = '' + ndo_array = False + if True in [destroy_txt in proto.text for destroy_txt in ['Destroy', 'Free']]: + # Check for special case where multiple handles are returned + if cmd_info[-1].len is not None: + ndo_array = True; + param = -1 + else: + param = -2 + if self.isHandleTypeNonDispatchable(cmd_info[param].type) == True: + if ndo_array == True: + # This API is freeing an array of handles. Remove them from the unique_id map. + destroy_ndo_code += '%sif ((VK_SUCCESS == result) && (%s)) {\n' % (indent, cmd_info[param].name) + indent = self.incIndent(indent) + destroy_ndo_code += '%sstd::unique_lock lock(global_lock);\n' % (indent) + destroy_ndo_code += '%sfor (uint32_t index0 = 0; index0 < %s; index0++) {\n' % (indent, cmd_info[param].len) + indent = self.incIndent(indent) + destroy_ndo_code += '%s%s handle = %s[index0];\n' % (indent, cmd_info[param].type, cmd_info[param].name) + destroy_ndo_code += '%suint64_t unique_id = reinterpret_cast(handle);\n' % (indent) + destroy_ndo_code += '%sdev_data->unique_id_mapping.erase(unique_id);\n' % (indent) + indent = self.decIndent(indent); + destroy_ndo_code += '%s}\n' % indent + indent = self.decIndent(indent); + destroy_ndo_code += '%s}\n' % indent + else: + # Remove a single handle from the map + destroy_ndo_code += '%sstd::unique_lock lock(global_lock);\n' % (indent) + destroy_ndo_code += '%suint64_t %s_id = reinterpret_cast(%s);\n' % (indent, cmd_info[param].name, cmd_info[param].name) + destroy_ndo_code += '%s%s = (%s)dev_data->unique_id_mapping[%s_id];\n' % (indent, cmd_info[param].name, cmd_info[param].type, cmd_info[param].name) + destroy_ndo_code += '%sdev_data->unique_id_mapping.erase(%s_id);\n' % (indent, cmd_info[param].name) + destroy_ndo_code += '%slock.unlock();\n' % (indent) + return ndo_array, destroy_ndo_code + + # + # Clean up local declarations + def cleanUpLocalDeclarations(self, indent, prefix, name, len): + cleanup = '%sif (local_%s%s)\n' % (indent, prefix, name) + if len is not None: + cleanup += '%s delete[] local_%s%s;\n' % (indent, prefix, name) + else: + cleanup += '%s delete local_%s%s;\n' % (indent, prefix, name) + return cleanup + # + # Output UO code for a single NDO (ndo_count is NULL) or a counted list of NDOs + def outputNDOs(self, ndo_type, ndo_name, ndo_count, prefix, index, indent, destroy_func, destroy_array, top_level): + decl_code = '' + pre_call_code = '' + post_call_code = '' + if ndo_count is not None: + if top_level == True: + decl_code += '%s%s *local_%s%s = NULL;\n' % (indent, ndo_type, prefix, ndo_name) + pre_call_code += '%s if (%s%s) {\n' % (indent, prefix, ndo_name) + indent = self.incIndent(indent) + if top_level == True: + pre_call_code += '%s local_%s%s = new %s[%s];\n' % (indent, prefix, ndo_name, ndo_type, ndo_count) + pre_call_code += '%s for (uint32_t %s = 0; %s < %s; ++%s) {\n' % (indent, index, index, ndo_count, index) + indent = self.incIndent(indent) + pre_call_code += '%s local_%s%s[%s] = (%s)dev_data->unique_id_mapping[reinterpret_cast(%s[%s])];\n' % (indent, prefix, ndo_name, index, ndo_type, ndo_name, index) + else: + pre_call_code += '%s for (uint32_t %s = 0; %s < %s; ++%s) {\n' % (indent, index, index, ndo_count, index) + indent = self.incIndent(indent) + pre_call_code += '%s %s%s[%s] = (%s)dev_data->unique_id_mapping[reinterpret_cast(%s%s[%s])];\n' % (indent, prefix, ndo_name, index, ndo_type, prefix, ndo_name, index) + indent = self.decIndent(indent) + pre_call_code += '%s }\n' % indent + indent = self.decIndent(indent) + pre_call_code += '%s }\n' % indent + if top_level == True: + post_call_code += '%sif (local_%s%s)\n' % (indent, prefix, ndo_name) + indent = self.incIndent(indent) + post_call_code += '%sdelete[] local_%s;\n' % (indent, ndo_name) + else: + if top_level == True: + if (destroy_func == False) or (destroy_array == True): #### LUGMAL This line needs to be skipped for destroy_ndo and not destroy_array + pre_call_code += '%s %s = (%s)dev_data->unique_id_mapping[reinterpret_cast(%s)];\n' % (indent, ndo_name, ndo_type, ndo_name) + else: + # Make temp copy of this var with the 'local' removed. It may be better to not pass in 'local_' + # as part of the string and explicitly print it + fix = str(prefix).strip('local_'); + pre_call_code += '%s if (%s%s) {\n' % (indent, fix, ndo_name) + indent = self.incIndent(indent) + pre_call_code += '%s %s%s = (%s)dev_data->unique_id_mapping[reinterpret_cast(%s%s)];\n' % (indent, prefix, ndo_name, ndo_type, fix, ndo_name) + indent = self.decIndent(indent) + pre_call_code += '%s }\n' % indent + return decl_code, pre_call_code, post_call_code + # + # first_level_param indicates if elements are passed directly into the function else they're below a ptr/struct + # create_func means that this is API creates or allocates NDOs + # destroy_func indicates that this API destroys or frees NDOs + # destroy_array means that the destroy_func operated on an array of NDOs + def uniquify_members(self, members, indent, prefix, array_index, create_func, destroy_func, destroy_array, first_level_param): + decls = '' + pre_code = '' + post_code = '' + struct_member_dict = dict(self.structMembers) + index = 'index%s' % str(array_index) + array_index += 1 + # Process any NDOs in this structure and recurse for any sub-structs in this struct + for member in members: + # Handle NDOs + if self.isHandleTypeNonDispatchable(member.type) == True: + count_name = member.len + if (count_name is not None): + if first_level_param == False: + count_name = '%s%s' % (prefix, member.len) + + if (first_level_param == False) or (create_func == False): + (tmp_decl, tmp_pre, tmp_post) = self.outputNDOs(member.type, member.name, count_name, prefix, index, indent, destroy_func, destroy_array, first_level_param) + decls += tmp_decl + pre_code += tmp_pre + post_code += tmp_post + # Handle Structs that contain NDOs at some level + elif member.type in struct_member_dict: + # All structs at first level will have an NDO + if self.struct_contains_ndo(member.type) == True: + struct_info = struct_member_dict[member.type] + # Struct Array + if member.len is not None: + # Update struct prefix + if first_level_param == True: + new_prefix = 'local_%s' % member.name + # Declare safe_VarType for struct + decls += '%ssafe_%s *%s = NULL;\n' % (indent, member.type, new_prefix) + else: + new_prefix = '%s%s' % (prefix, member.name) + pre_code += '%s if (%s%s) {\n' % (indent, prefix, member.name) + indent = self.incIndent(indent) + if first_level_param == True: + pre_code += '%s %s = new safe_%s[%s];\n' % (indent, new_prefix, member.type, member.len) + pre_code += '%s for (uint32_t %s = 0; %s < %s%s; ++%s) {\n' % (indent, index, index, prefix, member.len, index) + indent = self.incIndent(indent) + if first_level_param == True: + pre_code += '%s %s[%s].initialize(&%s[%s]);\n' % (indent, new_prefix, index, member.name, index) + local_prefix = '%s[%s].' % (new_prefix, index) + # Process sub-structs in this struct + (tmp_decl, tmp_pre, tmp_post) = self.uniquify_members(struct_info, indent, local_prefix, array_index, create_func, destroy_func, destroy_array, False) + decls += tmp_decl + pre_code += tmp_pre + post_code += tmp_post + indent = self.decIndent(indent) + pre_code += '%s }\n' % indent + indent = self.decIndent(indent) + pre_code += '%s }\n' % indent + if first_level_param == True: + post_code += self.cleanUpLocalDeclarations(indent, prefix, member.name, member.len) + # Single Struct + else: + # Update struct prefix + if first_level_param == True: + new_prefix = 'local_%s->' % member.name + decls += '%ssafe_%s *local_%s%s = NULL;\n' % (indent, member.type, prefix, member.name) + else: + new_prefix = '%s%s->' % (prefix, member.name) + # Declare safe_VarType for struct + pre_code += '%s if (%s%s) {\n' % (indent, prefix, member.name) + indent = self.incIndent(indent) + if first_level_param == True: + pre_code += '%s local_%s%s = new safe_%s(%s);\n' % (indent, prefix, member.name, member.type, member.name) + # Process sub-structs in this struct + (tmp_decl, tmp_pre, tmp_post) = self.uniquify_members(struct_info, indent, new_prefix, array_index, create_func, destroy_func, destroy_array, False) + decls += tmp_decl + pre_code += tmp_pre + post_code += tmp_post + indent = self.decIndent(indent) + pre_code += '%s }\n' % indent + if first_level_param == True: + post_code += self.cleanUpLocalDeclarations(indent, prefix, member.name, member.len) + return decls, pre_code, post_code + # + # For a particular API, generate the non-dispatchable-object wrapping/unwrapping code + def generate_wrapping_code(self, cmd): + indent = ' ' + proto = cmd.find('proto/name') + params = cmd.findall('param') + if proto.text is not None: + cmd_member_dict = dict(self.cmdMembers) + cmd_info = cmd_member_dict[proto.text] + # Handle ndo create/allocate operations + if cmd_info[0].iscreate: + create_ndo_code = self.generate_create_ndo_code(indent, proto, params, cmd_info) + else: + create_ndo_code = '' + # Handle ndo destroy/free operations + if cmd_info[0].isdestroy: + (destroy_array, destroy_ndo_code) = self.generate_destroy_ndo_code(indent, proto, cmd_info) + else: + destroy_array = False + destroy_ndo_code = '' + paramdecl = '' + param_pre_code = '' + param_post_code = '' + create_func = True if create_ndo_code else False + destroy_func = True if destroy_ndo_code else False + (paramdecl, param_pre_code, param_post_code) = self.uniquify_members(cmd_info, indent, '', 0, create_func, destroy_func, destroy_array, True) + param_post_code += create_ndo_code + if destroy_ndo_code: + if destroy_array == True: + param_post_code += destroy_ndo_code + else: + param_pre_code += destroy_ndo_code + if param_pre_code: + if (not destroy_func) or (destroy_array): + param_pre_code = '%s{\n%s%s%s%s}\n' % (' ', indent, self.lock_guard(indent), param_pre_code, indent) + return paramdecl, param_pre_code, param_post_code + # + # Capture command parameter info needed to wrap NDOs as well as handling some boilerplate code + def genCmd(self, cmdinfo, cmdname): + if cmdname in self.interface_functions: + return + if cmdname in self.no_autogen_list: + decls = self.makeCDecls(cmdinfo.elem) + self.appendSection('command', '') + self.appendSection('command', '// Declare only') + self.appendSection('command', decls[0]) + self.intercepts += [ ' {"%s", reinterpret_cast(%s)},' % (cmdname,cmdname[2:]) ] + return + # Add struct-member type information to command parameter information + OutputGenerator.genCmd(self, cmdinfo, cmdname) + members = cmdinfo.elem.findall('.//param') + # 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) + struct_member_dict = dict(self.structMembers) + # Generate member info + membersInfo = [] + for member in members: + # Get type and name of member + info = self.getTypeNameTuple(member) + type = info[0] + name = info[1] + cdecl = self.makeCParamDecl(member, 0) + # Check for parameter name in lens set + iscount = True if name in lens else False + len = self.getLen(member) + isconst = True if 'const' in cdecl else False + ispointer = self.paramIsPointer(member) + # Mark param as local if it is an array of NDOs + islocal = False; + if self.isHandleTypeNonDispatchable(type) == True: + if (len is not None) and (isconst == True): + islocal = True + # Or if it's a struct that contains an NDO + elif type in struct_member_dict: + if self.struct_contains_ndo(type) == True: + islocal = True + + isdestroy = True if True in [destroy_txt in cmdname for destroy_txt in ['Destroy', 'Free']] else False + iscreate = True if True in [create_txt in cmdname for create_txt in ['Create', 'Allocate']] else False + + membersInfo.append(self.CommandParam(type=type, + name=name, + ispointer=ispointer, + isconst=isconst, + iscount=iscount, + len=len, + extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None, + cdecl=cdecl, + islocal=islocal, + iscreate=iscreate, + isdestroy=isdestroy)) + self.cmdMembers.append(self.CmdMemberData(name=cmdname, members=membersInfo)) + # Generate NDO wrapping/unwrapping code for all parameters + (api_decls, api_pre, api_post) = self.generate_wrapping_code(cmdinfo.elem) + # If API doesn't contain an NDO's, don't fool with it + if not api_decls and not api_pre and not api_post: + return + # Record that the function will be intercepted + if (self.featureExtraProtect != None): + self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ] + self.intercepts += [ ' {"%s", reinterpret_cast(%s)},' % (cmdname,cmdname[2:]) ] + if (self.featureExtraProtect != None): + self.intercepts += [ '#endif' ] + decls = self.makeCDecls(cmdinfo.elem) + self.appendSection('command', '') + self.appendSection('command', decls[0][:-1]) + self.appendSection('command', '{') + # Setup common to call wrappers, first parameter is always dispatchable + dispatchable_type = cmdinfo.elem.find('param/type').text + dispatchable_name = cmdinfo.elem.find('param/name').text + # Generate local instance/pdev/device data lookup + self.appendSection('command', ' layer_data *dev_data = get_my_data_ptr(get_dispatch_key('+dispatchable_name+'), layer_data_map);') + # Handle return values, if any + resulttype = cmdinfo.elem.find('proto/type') + if (resulttype != None and resulttype.text == 'void'): + resulttype = None + if (resulttype != None): + assignresult = resulttype.text + ' result = ' + else: + assignresult = '' + # Pre-pend declarations and pre-api-call codegen + if api_decls: + self.appendSection('command', "\n".join(str(api_decls).rstrip().split("\n"))) + if api_pre: + self.appendSection('command', "\n".join(str(api_pre).rstrip().split("\n"))) + # Generate the API call itself + # Gather the parameter items + params = cmdinfo.elem.findall('param/name') + # Pull out the text for each of the parameters, separate them by commas in a list + paramstext = ', '.join([str(param.text) for param in params]) + # If any of these paramters has been replaced by a local var, fix up the list + cmd_member_dict = dict(self.cmdMembers) + params = cmd_member_dict[cmdname] + for param in params: + if param.islocal == True: + if param.ispointer == True: + paramstext = paramstext.replace(param.name, '(%s %s*)local_%s' % ('const', param.type, param.name)) + else: + paramstext = paramstext.replace(param.name, '(%s %s)local_%s' % ('const', param.type, param.name)) + # Use correct dispatch table + if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]: + API = cmdinfo.elem.attrib.get('name').replace('vk','dev_data->instance_dispatch_table->',1) + else: + API = cmdinfo.elem.attrib.get('name').replace('vk','dev_data->device_dispatch_table->',1) + # Put all this together for the final down-chain call + self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');') + # And add the post-API-call codegen + self.appendSection('command', "\n".join(str(api_post).rstrip().split("\n"))) + # Handle the return result variable, if any + if (resulttype != None): + self.appendSection('command', ' return result;') + self.appendSection('command', '}') diff --git a/threading_generator.py b/threading_generator.py deleted file mode 100644 index 0d0df12a..00000000 --- a/threading_generator.py +++ /dev/null @@ -1,467 +0,0 @@ -#!/usr/bin/python3 -i -# -# Copyright (c) 2015-2016 The Khronos Group Inc. -# Copyright (c) 2015-2016 Valve Corporation -# Copyright (c) 2015-2016 LunarG, Inc. -# Copyright (c) 2015-2016 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: Mike Stroyan - -import os,re,sys -from generator import * - -# ThreadGeneratorOptions - subclass of GeneratorOptions. -# -# Adds options used by ThreadOutputGenerator objects during threading -# layer generation. -# -# Additional members -# prefixText - list of strings to prefix generated header with -# (usually a copyright statement + calling convention macros). -# protectFile - True if multiple inclusion protection should be -# generated (based on the filename) around the entire header. -# protectFeature - True if #ifndef..#endif protection should be -# generated around a feature interface in the header file. -# genFuncPointers - True if function pointer typedefs should be -# generated -# protectProto - If conditional protection should be generated -# around prototype declarations, set to either '#ifdef' -# to require opt-in (#ifdef protectProtoStr) or '#ifndef' -# to require opt-out (#ifndef protectProtoStr). Otherwise -# set to None. -# protectProtoStr - #ifdef/#ifndef symbol to use around prototype -# declarations, if protectProto is set -# apicall - string to use for the function declaration prefix, -# such as APICALL on Windows. -# apientry - string to use for the calling convention macro, -# in typedefs, such as APIENTRY. -# apientryp - string to use for the calling convention macro -# in function pointer typedefs, such as APIENTRYP. -# indentFuncProto - True if prototype declarations should put each -# parameter on a separate line -# indentFuncPointer - True if typedefed function pointers should put each -# parameter on a separate line -# alignFuncParam - if nonzero and parameters are being put on a -# separate line, align parameter names at the specified column -class ThreadGeneratorOptions(GeneratorOptions): - def __init__(self, - filename = None, - directory = '.', - apiname = None, - profile = None, - versions = '.*', - emitversions = '.*', - defaultExtensions = None, - addExtensions = None, - removeExtensions = None, - sortProcedure = regSortFeatures, - prefixText = "", - genFuncPointers = True, - protectFile = True, - protectFeature = True, - protectProto = None, - protectProtoStr = None, - apicall = '', - apientry = '', - apientryp = '', - indentFuncProto = True, - indentFuncPointer = False, - alignFuncParam = 0): - GeneratorOptions.__init__(self, filename, directory, apiname, profile, - versions, emitversions, defaultExtensions, - addExtensions, removeExtensions, sortProcedure) - self.prefixText = prefixText - self.genFuncPointers = genFuncPointers - self.protectFile = protectFile - self.protectFeature = protectFeature - self.protectProto = protectProto - self.protectProtoStr = protectProtoStr - self.apicall = apicall - self.apientry = apientry - self.apientryp = apientryp - self.indentFuncProto = indentFuncProto - self.indentFuncPointer = indentFuncPointer - self.alignFuncParam = alignFuncParam - -# ThreadOutputGenerator - subclass of OutputGenerator. -# Generates Thread checking framework -# -# ---- methods ---- -# ThreadOutputGenerator(errFile, warnFile, diagFile) - args as for -# OutputGenerator. Defines additional internal state. -# ---- methods overriding base class ---- -# beginFile(genOpts) -# endFile() -# beginFeature(interface, emit) -# endFeature() -# genType(typeinfo,name) -# genStruct(typeinfo,name) -# genGroup(groupinfo,name) -# genEnum(enuminfo, name) -# genCmd(cmdinfo) -class ThreadOutputGenerator(OutputGenerator): - """Generate specified API interfaces in a specific style, such as a C header""" - # This is an ordered list of sections in the header file. - TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', - 'group', 'bitmask', 'funcpointer', 'struct'] - ALL_SECTIONS = TYPE_SECTIONS + ['command'] - 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.sections = dict([(section, []) for section in self.ALL_SECTIONS]) - self.intercepts = [] - - # Check if the parameter passed in is a pointer to an array - def paramIsArray(self, param): - return param.attrib.get('len') is not None - - # Check if the parameter passed in is a pointer - def paramIsPointer(self, param): - ispointer = False - for elem in param: - #write('paramIsPointer '+elem.text, file=sys.stderr) - #write('elem.tag '+elem.tag, file=sys.stderr) - #if (elem.tail is None): - # write('elem.tail is None', file=sys.stderr) - #else: - # write('elem.tail '+elem.tail, file=sys.stderr) - if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail: - ispointer = True - # write('is pointer', file=sys.stderr) - return ispointer - def makeThreadUseBlock(self, cmd, functionprefix): - """Generate C function pointer typedef for Element""" - paramdecl = '' - thread_check_dispatchable_objects = [ - "VkCommandBuffer", - "VkDevice", - "VkInstance", - "VkQueue", - ] - thread_check_nondispatchable_objects = [ - "VkBuffer", - "VkBufferView", - "VkCommandPool", - "VkDescriptorPool", - "VkDescriptorSetLayout", - "VkDeviceMemory", - "VkEvent", - "VkFence", - "VkFramebuffer", - "VkImage", - "VkImageView", - "VkPipeline", - "VkPipelineCache", - "VkPipelineLayout", - "VkQueryPool", - "VkRenderPass", - "VkSampler", - "VkSemaphore", - "VkShaderModule", - ] - - # Find and add any parameters that are thread unsafe - params = cmd.findall('param') - for param in params: - paramname = param.find('name') - if False: # self.paramIsPointer(param): - paramdecl += ' // not watching use of pointer ' + paramname.text + '\n' - else: - externsync = param.attrib.get('externsync') - if externsync == 'true': - if self.paramIsArray(param): - paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n' - paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + '[index]);\n' - paramdecl += ' }\n' - else: - paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + ');\n' - elif (param.attrib.get('externsync')): - if self.paramIsArray(param): - # Externsync can list pointers to arrays of members to synchronize - paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n' - for member in externsync.split(","): - # Replace first empty [] in member name with index - element = member.replace('[]','[index]',1) - if '[]' in element: - # Replace any second empty [] in element name with - # inner array index based on mapping array names like - # "pSomeThings[]" to "someThingCount" array size. - # This could be more robust by mapping a param member - # name to a struct type and "len" attribute. - limit = element[0:element.find('s[]')] + 'Count' - dotp = limit.rfind('.p') - limit = limit[0:dotp+1] + limit[dotp+2:dotp+3].lower() + limit[dotp+3:] - paramdecl += ' for(uint32_t index2=0;index2<'+limit+';index2++)\n' - element = element.replace('[]','[index2]') - paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + element + ');\n' - paramdecl += ' }\n' - else: - # externsync can list members to synchronize - for member in externsync.split(","): - member = str(member).replace("::", "->") - paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + member + ');\n' - else: - paramtype = param.find('type') - if paramtype is not None: - paramtype = paramtype.text - else: - paramtype = 'None' - if paramtype in thread_check_dispatchable_objects or paramtype in thread_check_nondispatchable_objects: - if self.paramIsArray(param) and ('pPipelines' != paramname.text): - paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n' - paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + '[index]);\n' - paramdecl += ' }\n' - elif not self.paramIsPointer(param): - # Pointer params are often being created. - # They are not being read from. - paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + ');\n' - explicitexternsyncparams = cmd.findall("param[@externsync]") - if (explicitexternsyncparams is not None): - for param in explicitexternsyncparams: - externsyncattrib = param.attrib.get('externsync') - paramname = param.find('name') - paramdecl += ' // Host access to ' - if externsyncattrib == 'true': - if self.paramIsArray(param): - paramdecl += 'each member of ' + paramname.text - elif self.paramIsPointer(param): - paramdecl += 'the object referenced by ' + paramname.text - else: - paramdecl += paramname.text - else: - paramdecl += externsyncattrib - paramdecl += ' must be externally synchronized\n' - - # Find and add any "implicit" parameters that are thread unsafe - implicitexternsyncparams = cmd.find('implicitexternsyncparams') - if (implicitexternsyncparams is not None): - for elem in implicitexternsyncparams: - paramdecl += ' // ' - paramdecl += elem.text - paramdecl += ' must be externally synchronized between host accesses\n' - - if (paramdecl == ''): - return None - else: - return paramdecl - def beginFile(self, genOpts): - OutputGenerator.beginFile(self, genOpts) - # C-specific - # - # Multiple inclusion protection & C++ namespace. - if (genOpts.protectFile and self.genOpts.filename): - headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename)) - write('#ifndef', headerSym, file=self.outFile) - write('#define', headerSym, '1', file=self.outFile) - self.newline() - write('namespace threading {', file=self.outFile) - self.newline() - # - # User-supplied prefix text, if any (list of strings) - if (genOpts.prefixText): - for s in genOpts.prefixText: - write(s, file=self.outFile) - def endFile(self): - # C-specific - # Finish C++ namespace and multiple inclusion protection - self.newline() - # record intercepted procedures - write('// intercepts', file=self.outFile) - write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile) - write('\n'.join(self.intercepts), file=self.outFile) - write('};\n', file=self.outFile) - self.newline() - write('} // namespace threading', file=self.outFile) - if (self.genOpts.protectFile and self.genOpts.filename): - self.newline() - write('#endif', file=self.outFile) - # Finish processing in superclass - OutputGenerator.endFile(self) - def beginFeature(self, interface, emit): - #write('// starting beginFeature', file=self.outFile) - # Start processing in superclass - OutputGenerator.beginFeature(self, interface, emit) - # C-specific - # Accumulate includes, defines, types, enums, function pointer typedefs, - # end function prototypes separately for this feature. They're only - # printed in endFeature(). - self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) - #write('// ending beginFeature', file=self.outFile) - def endFeature(self): - # C-specific - # Actually write the interface to the output file. - #write('// starting endFeature', file=self.outFile) - if (self.emit): - self.newline() - if (self.genOpts.protectFeature): - write('#ifndef', self.featureName, file=self.outFile) - # If type declarations are needed by other features based on - # this one, it may be necessary to suppress the ExtraProtect, - # or move it below the 'for section...' loop. - #write('// endFeature looking at self.featureExtraProtect', file=self.outFile) - if (self.featureExtraProtect != None): - write('#ifdef', self.featureExtraProtect, file=self.outFile) - #write('#define', self.featureName, '1', file=self.outFile) - for section in self.TYPE_SECTIONS: - #write('// endFeature writing section'+section, file=self.outFile) - contents = self.sections[section] - if contents: - write('\n'.join(contents), file=self.outFile) - self.newline() - #write('// endFeature looking at self.sections[command]', file=self.outFile) - if (self.sections['command']): - write('\n'.join(self.sections['command']), end='', file=self.outFile) - self.newline() - if (self.featureExtraProtect != None): - write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) - if (self.genOpts.protectFeature): - write('#endif /*', self.featureName, '*/', file=self.outFile) - # Finish processing in superclass - OutputGenerator.endFeature(self) - #write('// ending endFeature', file=self.outFile) - # - # Append a definition to the specified section - def appendSection(self, section, text): - # self.sections[section].append('SECTION: ' + section + '\n') - self.sections[section].append(text) - # - # Type generation - def genType(self, typeinfo, name): - pass - # - # Struct (e.g. C "struct" type) generation. - # This is a special case of the tag where the contents are - # interpreted as a set of tags instead of freeform C - # C type declarations. The tags are just like - # tags - they are a declaration of a struct or union member. - # Only simple member declarations are supported (no nested - # structs etc.) - def genStruct(self, typeinfo, typeName): - OutputGenerator.genStruct(self, typeinfo, typeName) - body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n' - # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam) - for member in typeinfo.elem.findall('.//member'): - body += self.makeCParamDecl(member, self.genOpts.alignFuncParam) - body += ';\n' - body += '} ' + typeName + ';\n' - self.appendSection('struct', body) - # - # Group (e.g. C "enum" type) generation. - # These are concatenated together with other types. - def genGroup(self, groupinfo, groupName): - pass - # Enumerant generation - # tags may specify their values in several ways, but are usually - # just integers. - def genEnum(self, enuminfo, name): - pass - # - # Command generation - def genCmd(self, cmdinfo, name): - # Commands shadowed by interface functions and are not implemented - interface_functions = [ - 'vkEnumerateInstanceLayerProperties', - 'vkEnumerateInstanceExtensionProperties', - 'vkEnumerateDeviceLayerProperties', - ] - if name in interface_functions: - return - special_functions = [ - 'vkGetDeviceProcAddr', - 'vkGetInstanceProcAddr', - 'vkCreateDevice', - 'vkDestroyDevice', - 'vkCreateInstance', - 'vkDestroyInstance', - 'vkAllocateCommandBuffers', - 'vkFreeCommandBuffers', - 'vkCreateDebugReportCallbackEXT', - 'vkDestroyDebugReportCallbackEXT', - ] - if name in special_functions: - decls = self.makeCDecls(cmdinfo.elem) - self.appendSection('command', '') - self.appendSection('command', '// declare only') - self.appendSection('command', decls[0]) - self.intercepts += [ ' {"%s", reinterpret_cast(%s)},' % (name,name[2:]) ] - return - if "KHR" in name: - self.appendSection('command', '// TODO - not wrapping KHR function ' + name) - return - if ("DebugMarker" in name) and ("EXT" in name): - self.appendSection('command', '// TODO - not wrapping EXT function ' + name) - return - # Determine first if this function needs to be intercepted - startthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'start') - if startthreadsafety is None: - return - finishthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'finish') - # record that the function will be intercepted - if (self.featureExtraProtect != None): - self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ] - self.intercepts += [ ' {"%s", reinterpret_cast(%s)},' % (name,name[2:]) ] - if (self.featureExtraProtect != None): - self.intercepts += [ '#endif' ] - - OutputGenerator.genCmd(self, cmdinfo, name) - # - decls = self.makeCDecls(cmdinfo.elem) - self.appendSection('command', '') - self.appendSection('command', decls[0][:-1]) - self.appendSection('command', '{') - # setup common to call wrappers - # first parameter is always dispatchable - dispatchable_type = cmdinfo.elem.find('param/type').text - dispatchable_name = cmdinfo.elem.find('param/name').text - self.appendSection('command', ' dispatch_key key = get_dispatch_key('+dispatchable_name+');') - self.appendSection('command', ' layer_data *my_data = get_my_data_ptr(key, layer_data_map);') - if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]: - self.appendSection('command', ' VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table;') - else: - self.appendSection('command', ' VkLayerDispatchTable *pTable = my_data->device_dispatch_table;') - # Declare result variable, if any. - resulttype = cmdinfo.elem.find('proto/type') - if (resulttype != None and resulttype.text == 'void'): - resulttype = None - if (resulttype != None): - self.appendSection('command', ' ' + resulttype.text + ' result;') - assignresult = 'result = ' - else: - assignresult = '' - - self.appendSection('command', ' bool threadChecks = startMultiThread();') - self.appendSection('command', ' if (threadChecks) {') - self.appendSection('command', " "+"\n ".join(str(startthreadsafety).rstrip().split("\n"))) - self.appendSection('command', ' }') - params = cmdinfo.elem.findall('param/name') - paramstext = ','.join([str(param.text) for param in params]) - API = cmdinfo.elem.attrib.get('name').replace('vk','pTable->',1) - self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');') - self.appendSection('command', ' if (threadChecks) {') - self.appendSection('command', " "+"\n ".join(str(finishthreadsafety).rstrip().split("\n"))) - self.appendSection('command', ' } else {') - self.appendSection('command', ' finishMultiThread();') - self.appendSection('command', ' }') - # Return result variable, if any. - if (resulttype != None): - self.appendSection('command', ' return result;') - self.appendSection('command', '}') - # - # override makeProtoName to drop the "vk" prefix - def makeProtoName(self, name, tail): - return self.genOpts.apientry + name[2:] + tail diff --git a/unique_objects_generator.py b/unique_objects_generator.py deleted file mode 100644 index cdd2808c..00000000 --- a/unique_objects_generator.py +++ /dev/null @@ -1,760 +0,0 @@ -#!/usr/bin/python3 -i -# -# Copyright (c) 2015-2016 The Khronos Group Inc. -# Copyright (c) 2015-2016 Valve Corporation -# Copyright (c) 2015-2016 LunarG, Inc. -# Copyright (c) 2015-2016 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: Tobin Ehlis -# Author: Mark Lobodzinski - -import os,re,sys -import xml.etree.ElementTree as etree -from generator import * -from collections import namedtuple - -# UniqueObjectsGeneratorOptions - subclass of GeneratorOptions. -# -# Adds options used by UniqueObjectsOutputGenerator objects during -# unique objects layer generation. -# -# Additional members -# prefixText - list of strings to prefix generated header with -# (usually a copyright statement + calling convention macros). -# protectFile - True if multiple inclusion protection should be -# generated (based on the filename) around the entire header. -# protectFeature - True if #ifndef..#endif protection should be -# generated around a feature interface in the header file. -# genFuncPointers - True if function pointer typedefs should be -# generated -# protectProto - If conditional protection should be generated -# around prototype declarations, set to either '#ifdef' -# to require opt-in (#ifdef protectProtoStr) or '#ifndef' -# to require opt-out (#ifndef protectProtoStr). Otherwise -# set to None. -# protectProtoStr - #ifdef/#ifndef symbol to use around prototype -# declarations, if protectProto is set -# apicall - string to use for the function declaration prefix, -# such as APICALL on Windows. -# apientry - string to use for the calling convention macro, -# in typedefs, such as APIENTRY. -# apientryp - string to use for the calling convention macro -# in function pointer typedefs, such as APIENTRYP. -# indentFuncProto - True if prototype declarations should put each -# parameter on a separate line -# indentFuncPointer - True if typedefed function pointers should put each -# parameter on a separate line -# alignFuncParam - if nonzero and parameters are being put on a -# separate line, align parameter names at the specified column -class UniqueObjectsGeneratorOptions(GeneratorOptions): - def __init__(self, - filename = None, - directory = '.', - apiname = None, - profile = None, - versions = '.*', - emitversions = '.*', - defaultExtensions = None, - addExtensions = None, - removeExtensions = None, - sortProcedure = regSortFeatures, - prefixText = "", - genFuncPointers = True, - protectFile = True, - protectFeature = True, - protectProto = None, - protectProtoStr = None, - apicall = '', - apientry = '', - apientryp = '', - indentFuncProto = True, - indentFuncPointer = False, - alignFuncParam = 0): - GeneratorOptions.__init__(self, filename, directory, apiname, profile, - versions, emitversions, defaultExtensions, - addExtensions, removeExtensions, sortProcedure) - self.prefixText = prefixText - self.genFuncPointers = genFuncPointers - self.protectFile = protectFile - self.protectFeature = protectFeature - self.protectProto = protectProto - self.protectProtoStr = protectProtoStr - self.apicall = apicall - self.apientry = apientry - self.apientryp = apientryp - self.indentFuncProto = indentFuncProto - self.indentFuncPointer = indentFuncPointer - self.alignFuncParam = alignFuncParam - -# UniqueObjectsOutputGenerator - subclass of OutputGenerator. -# Generates unique objects layer non-dispatchable handle-wrapping code. -# -# ---- methods ---- -# UniqueObjectsOutputGenerator(errFile, warnFile, diagFile) - args as for OutputGenerator. Defines additional internal state. -# ---- methods overriding base class ---- -# beginFile(genOpts) -# endFile() -# beginFeature(interface, emit) -# endFeature() -# genCmd(cmdinfo) -# genStruct() -# genType() -class UniqueObjectsOutputGenerator(OutputGenerator): - """Generate UniqueObjects code based on XML element attributes""" - # This is an ordered list of sections in the header file. - ALL_SECTIONS = ['command'] - def __init__(self, - errFile = sys.stderr, - warnFile = sys.stderr, - diagFile = sys.stdout): - OutputGenerator.__init__(self, errFile, warnFile, diagFile) - self.INDENT_SPACES = 4 - # Commands to ignore - self.intercepts = [] - # Commands which are not autogenerated but still intercepted - self.no_autogen_list = [ - 'vkGetDeviceProcAddr', - 'vkGetInstanceProcAddr', - 'vkCreateInstance', - 'vkDestroyInstance', - 'vkCreateDevice', - 'vkDestroyDevice', - 'vkAllocateMemory', - 'vkCreateComputePipelines', - 'vkCreateGraphicsPipelines', - 'vkCreateSwapchainKHR', - 'vkGetSwapchainImagesKHR', - 'vkEnumerateInstanceLayerProperties', - 'vkEnumerateDeviceLayerProperties', - 'vkEnumerateInstanceExtensionProperties', - ] - # Commands shadowed by interface functions and are not implemented - self.interface_functions = [ - 'vkGetPhysicalDeviceDisplayPropertiesKHR', - 'vkGetPhysicalDeviceDisplayPlanePropertiesKHR', - 'vkGetDisplayPlaneSupportedDisplaysKHR', - 'vkGetDisplayModePropertiesKHR', - # DebugReport APIs are hooked, but handled separately in the source file - 'vkCreateDebugReportCallbackEXT', - 'vkDestroyDebugReportCallbackEXT', - 'vkDebugReportMessageEXT', - ] - self.headerVersion = None - # Internal state - accumulators for different inner block text - self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) - self.structNames = [] # List of Vulkan struct typenames - self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType - self.handleTypes = set() # Set of handle type names - self.commands = [] # List of CommandData records for all Vulkan commands - self.structMembers = [] # List of StructMemberData records for all Vulkan structs - self.flags = set() # Map of flags typenames - # Named tuples to store struct and command data - self.StructType = namedtuple('StructType', ['name', 'value']) - self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isconst', 'iscount', 'len', 'extstructs', 'cdecl', 'islocal', 'iscreate', 'isdestroy']) - self.CommandData = namedtuple('CommandData', ['name', 'return_type', 'params', 'cdecl']) - self.StructMemberData = namedtuple('StructMemberData', ['name', 'members']) - # - def incIndent(self, indent): - inc = ' ' * self.INDENT_SPACES - if indent: - return indent + inc - return inc - # - def decIndent(self, indent): - if indent and (len(indent) > self.INDENT_SPACES): - return indent[:-self.INDENT_SPACES] - return '' - # - # Override makeProtoName to drop the "vk" prefix - def makeProtoName(self, name, tail): - return self.genOpts.apientry + name[2:] + tail - # - # Check if the parameter passed in is a pointer to an array - def paramIsArray(self, param): - return param.attrib.get('len') is not None - # - def beginFile(self, genOpts): - OutputGenerator.beginFile(self, genOpts) - # User-supplied prefix text, if any (list of strings) - if (genOpts.prefixText): - for s in genOpts.prefixText: - write(s, file=self.outFile) - # Namespace - self.newline() - write('namespace unique_objects {', file = self.outFile) - # - def endFile(self): - self.newline() - # Record intercepted procedures - write('// intercepts', file=self.outFile) - write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile) - write('\n'.join(self.intercepts), file=self.outFile) - write('};\n', file=self.outFile) - self.newline() - write('} // namespace unique_objects', file=self.outFile) - # Finish processing in superclass - OutputGenerator.endFile(self) - # - def beginFeature(self, interface, emit): - # Start processing in superclass - OutputGenerator.beginFeature(self, interface, emit) - self.headerVersion = None - self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) - self.structNames = [] - self.structTypes = dict() - self.handleTypes = set() - self.commands = [] - self.structMembers = [] - self.cmdMembers = [] - self.flags = set() - self.StructMemberData = namedtuple('StructMemberData', ['name', 'members']) - self.CmdMemberData = namedtuple('CmdMemberData', ['name', 'members']) - # - def endFeature(self): - # Actually write the interface to the output file. - if (self.emit): - self.newline() - if (self.featureExtraProtect != None): - write('#ifdef', self.featureExtraProtect, file=self.outFile) - # Write the unique_objects code to the file - if (self.sections['command']): - if (self.genOpts.protectProto): - write(self.genOpts.protectProto, - self.genOpts.protectProtoStr, file=self.outFile) - write('\n'.join(self.sections['command']), end='', file=self.outFile) - if (self.featureExtraProtect != None): - write('\n#endif //', self.featureExtraProtect, file=self.outFile) - else: - self.newline() - # Finish processing in superclass - OutputGenerator.endFeature(self) - # - def genType(self, typeinfo, name): - OutputGenerator.genType(self, typeinfo, name) - typeElem = typeinfo.elem - # If the type is a struct type, traverse the imbedded tags generating a structure. - # Otherwise, emit the tag text. - category = typeElem.get('category') - if (category == 'struct' or category == 'union'): - self.structNames.append(name) - self.genStruct(typeinfo, name) - # - # Append a definition to the specified section - def appendSection(self, section, text): - # self.sections[section].append('SECTION: ' + section + '\n') - self.sections[section].append(text) - # - # Check if the parameter passed in is a pointer - def paramIsPointer(self, param): - ispointer = False - for elem in param: - if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail: - ispointer = True - return ispointer - # - # Get the category of a type - def getTypeCategory(self, typename): - types = self.registry.tree.findall("types/type") - for elem in types: - if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename: - return elem.attrib.get('category') - # - # Check if a parent object is dispatchable or not - def isHandleTypeNonDispatchable(self, handletype): - handle = self.registry.tree.find("types/type/[name='" + handletype + "'][@category='handle']") - if handle is not None and handle.find('type').text == 'VK_DEFINE_NON_DISPATCHABLE_HANDLE': - return True - else: - return False - # - # 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 - # Spec has now notation for len attributes, using :: instead of platform specific pointer symbol - result = str(result).replace('::', '->') - return result - # - # 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) - # - # Struct parameter check generation. - # This is a special case of the tag where the contents are interpreted as a set of - # tags instead of freeform C type declarations. The tags are just like - # tags - they are a declaration of a struct or union member. Only simple member - # declarations are supported (no nested structs etc.) - def genStruct(self, typeinfo, typeName): - OutputGenerator.genStruct(self, typeinfo, typeName) - 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, 0) - # 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 - membersInfo.append(self.CommandParam(type=type, - name=name, - ispointer=self.paramIsPointer(member), - isconst=True if 'const' in cdecl else False, - iscount=True if name in lens else False, - len=self.getLen(member), - extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None, - cdecl=cdecl, - islocal=False, - iscreate=False, - isdestroy=False)) - self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo)) - # - # Insert a lock_guard line - def lock_guard(self, indent): - return '%sstd::lock_guard lock(global_lock);\n' % indent - # - # Determine if a struct has an NDO as a member or an embedded member - def struct_contains_ndo(self, struct_item): - struct_member_dict = dict(self.structMembers) - struct_members = struct_member_dict[struct_item] - - for member in struct_members: - if self.isHandleTypeNonDispatchable(member.type): - return True - elif member.type in struct_member_dict: - if self.struct_contains_ndo(member.type) == True: - return True - return False - # - # Return list of struct members which contain, or which sub-structures contain - # an NDO in a given list of parameters or members - def getParmeterStructsWithNdos(self, item_list): - struct_list = set() - for item in item_list: - paramtype = item.find('type') - typecategory = self.getTypeCategory(paramtype.text) - if typecategory == 'struct': - if self.struct_contains_ndo(paramtype.text) == True: - struct_list.add(item) - return struct_list - # - # Return list of non-dispatchable objects from a given list of parameters or members - def getNdosInParameterList(self, item_list, create_func): - ndo_list = set() - if create_func == True: - member_list = item_list[0:-1] - else: - member_list = item_list - for item in member_list: - if self.isHandleTypeNonDispatchable(paramtype.text): - ndo_list.add(item) - return ndo_list - # - # Generate source for creating a non-dispatchable object - def generate_create_ndo_code(self, indent, proto, params, cmd_info): - create_ndo_code = '' - if True in [create_txt in proto.text for create_txt in ['Create', 'Allocate']]: - handle_type = params[-1].find('type') - if self.isHandleTypeNonDispatchable(handle_type.text): - # Check for special case where multiple handles are returned - ndo_array = False - if cmd_info[-1].len is not None: - ndo_array = True; - handle_name = params[-1].find('name') - create_ndo_code += '%sif (VK_SUCCESS == result) {\n' % (indent) - indent = self.incIndent(indent) - create_ndo_code += '%sstd::lock_guard lock(global_lock);\n' % (indent) - ndo_dest = '*%s' % handle_name.text - if ndo_array == True: - create_ndo_code += '%sfor (uint32_t index0 = 0; index0 < %s; index0++) {\n' % (indent, cmd_info[-1].len) - indent = self.incIndent(indent) - ndo_dest = '%s[index0]' % cmd_info[-1].name - create_ndo_code += '%suint64_t unique_id = global_unique_id++;\n' % (indent) - create_ndo_code += '%sdev_data->unique_id_mapping[unique_id] = reinterpret_cast(%s);\n' % (indent, ndo_dest) - create_ndo_code += '%s%s = reinterpret_cast<%s&>(unique_id);\n' % (indent, ndo_dest, handle_type.text) - if ndo_array == True: - indent = self.decIndent(indent) - create_ndo_code += '%s}\n' % indent - indent = self.decIndent(indent) - create_ndo_code += '%s}\n' % (indent) - return create_ndo_code - # - # Generate source for destroying a non-dispatchable object - def generate_destroy_ndo_code(self, indent, proto, cmd_info): - destroy_ndo_code = '' - ndo_array = False - if True in [destroy_txt in proto.text for destroy_txt in ['Destroy', 'Free']]: - # Check for special case where multiple handles are returned - if cmd_info[-1].len is not None: - ndo_array = True; - param = -1 - else: - param = -2 - if self.isHandleTypeNonDispatchable(cmd_info[param].type) == True: - if ndo_array == True: - # This API is freeing an array of handles. Remove them from the unique_id map. - destroy_ndo_code += '%sif ((VK_SUCCESS == result) && (%s)) {\n' % (indent, cmd_info[param].name) - indent = self.incIndent(indent) - destroy_ndo_code += '%sstd::unique_lock lock(global_lock);\n' % (indent) - destroy_ndo_code += '%sfor (uint32_t index0 = 0; index0 < %s; index0++) {\n' % (indent, cmd_info[param].len) - indent = self.incIndent(indent) - destroy_ndo_code += '%s%s handle = %s[index0];\n' % (indent, cmd_info[param].type, cmd_info[param].name) - destroy_ndo_code += '%suint64_t unique_id = reinterpret_cast(handle);\n' % (indent) - destroy_ndo_code += '%sdev_data->unique_id_mapping.erase(unique_id);\n' % (indent) - indent = self.decIndent(indent); - destroy_ndo_code += '%s}\n' % indent - indent = self.decIndent(indent); - destroy_ndo_code += '%s}\n' % indent - else: - # Remove a single handle from the map - destroy_ndo_code += '%sstd::unique_lock lock(global_lock);\n' % (indent) - destroy_ndo_code += '%suint64_t %s_id = reinterpret_cast(%s);\n' % (indent, cmd_info[param].name, cmd_info[param].name) - destroy_ndo_code += '%s%s = (%s)dev_data->unique_id_mapping[%s_id];\n' % (indent, cmd_info[param].name, cmd_info[param].type, cmd_info[param].name) - destroy_ndo_code += '%sdev_data->unique_id_mapping.erase(%s_id);\n' % (indent, cmd_info[param].name) - destroy_ndo_code += '%slock.unlock();\n' % (indent) - return ndo_array, destroy_ndo_code - - # - # Clean up local declarations - def cleanUpLocalDeclarations(self, indent, prefix, name, len): - cleanup = '%sif (local_%s%s)\n' % (indent, prefix, name) - if len is not None: - cleanup += '%s delete[] local_%s%s;\n' % (indent, prefix, name) - else: - cleanup += '%s delete local_%s%s;\n' % (indent, prefix, name) - return cleanup - # - # Output UO code for a single NDO (ndo_count is NULL) or a counted list of NDOs - def outputNDOs(self, ndo_type, ndo_name, ndo_count, prefix, index, indent, destroy_func, destroy_array, top_level): - decl_code = '' - pre_call_code = '' - post_call_code = '' - if ndo_count is not None: - if top_level == True: - decl_code += '%s%s *local_%s%s = NULL;\n' % (indent, ndo_type, prefix, ndo_name) - pre_call_code += '%s if (%s%s) {\n' % (indent, prefix, ndo_name) - indent = self.incIndent(indent) - if top_level == True: - pre_call_code += '%s local_%s%s = new %s[%s];\n' % (indent, prefix, ndo_name, ndo_type, ndo_count) - pre_call_code += '%s for (uint32_t %s = 0; %s < %s; ++%s) {\n' % (indent, index, index, ndo_count, index) - indent = self.incIndent(indent) - pre_call_code += '%s local_%s%s[%s] = (%s)dev_data->unique_id_mapping[reinterpret_cast(%s[%s])];\n' % (indent, prefix, ndo_name, index, ndo_type, ndo_name, index) - else: - pre_call_code += '%s for (uint32_t %s = 0; %s < %s; ++%s) {\n' % (indent, index, index, ndo_count, index) - indent = self.incIndent(indent) - pre_call_code += '%s %s%s[%s] = (%s)dev_data->unique_id_mapping[reinterpret_cast(%s%s[%s])];\n' % (indent, prefix, ndo_name, index, ndo_type, prefix, ndo_name, index) - indent = self.decIndent(indent) - pre_call_code += '%s }\n' % indent - indent = self.decIndent(indent) - pre_call_code += '%s }\n' % indent - if top_level == True: - post_call_code += '%sif (local_%s%s)\n' % (indent, prefix, ndo_name) - indent = self.incIndent(indent) - post_call_code += '%sdelete[] local_%s;\n' % (indent, ndo_name) - else: - if top_level == True: - if (destroy_func == False) or (destroy_array == True): #### LUGMAL This line needs to be skipped for destroy_ndo and not destroy_array - pre_call_code += '%s %s = (%s)dev_data->unique_id_mapping[reinterpret_cast(%s)];\n' % (indent, ndo_name, ndo_type, ndo_name) - else: - # Make temp copy of this var with the 'local' removed. It may be better to not pass in 'local_' - # as part of the string and explicitly print it - fix = str(prefix).strip('local_'); - pre_call_code += '%s if (%s%s) {\n' % (indent, fix, ndo_name) - indent = self.incIndent(indent) - pre_call_code += '%s %s%s = (%s)dev_data->unique_id_mapping[reinterpret_cast(%s%s)];\n' % (indent, prefix, ndo_name, ndo_type, fix, ndo_name) - indent = self.decIndent(indent) - pre_call_code += '%s }\n' % indent - return decl_code, pre_call_code, post_call_code - # - # first_level_param indicates if elements are passed directly into the function else they're below a ptr/struct - # create_func means that this is API creates or allocates NDOs - # destroy_func indicates that this API destroys or frees NDOs - # destroy_array means that the destroy_func operated on an array of NDOs - def uniquify_members(self, members, indent, prefix, array_index, create_func, destroy_func, destroy_array, first_level_param): - decls = '' - pre_code = '' - post_code = '' - struct_member_dict = dict(self.structMembers) - index = 'index%s' % str(array_index) - array_index += 1 - # Process any NDOs in this structure and recurse for any sub-structs in this struct - for member in members: - # Handle NDOs - if self.isHandleTypeNonDispatchable(member.type) == True: - count_name = member.len - if (count_name is not None): - if first_level_param == False: - count_name = '%s%s' % (prefix, member.len) - - if (first_level_param == False) or (create_func == False): - (tmp_decl, tmp_pre, tmp_post) = self.outputNDOs(member.type, member.name, count_name, prefix, index, indent, destroy_func, destroy_array, first_level_param) - decls += tmp_decl - pre_code += tmp_pre - post_code += tmp_post - # Handle Structs that contain NDOs at some level - elif member.type in struct_member_dict: - # All structs at first level will have an NDO - if self.struct_contains_ndo(member.type) == True: - struct_info = struct_member_dict[member.type] - # Struct Array - if member.len is not None: - # Update struct prefix - if first_level_param == True: - new_prefix = 'local_%s' % member.name - # Declare safe_VarType for struct - decls += '%ssafe_%s *%s = NULL;\n' % (indent, member.type, new_prefix) - else: - new_prefix = '%s%s' % (prefix, member.name) - pre_code += '%s if (%s%s) {\n' % (indent, prefix, member.name) - indent = self.incIndent(indent) - if first_level_param == True: - pre_code += '%s %s = new safe_%s[%s];\n' % (indent, new_prefix, member.type, member.len) - pre_code += '%s for (uint32_t %s = 0; %s < %s%s; ++%s) {\n' % (indent, index, index, prefix, member.len, index) - indent = self.incIndent(indent) - if first_level_param == True: - pre_code += '%s %s[%s].initialize(&%s[%s]);\n' % (indent, new_prefix, index, member.name, index) - local_prefix = '%s[%s].' % (new_prefix, index) - # Process sub-structs in this struct - (tmp_decl, tmp_pre, tmp_post) = self.uniquify_members(struct_info, indent, local_prefix, array_index, create_func, destroy_func, destroy_array, False) - decls += tmp_decl - pre_code += tmp_pre - post_code += tmp_post - indent = self.decIndent(indent) - pre_code += '%s }\n' % indent - indent = self.decIndent(indent) - pre_code += '%s }\n' % indent - if first_level_param == True: - post_code += self.cleanUpLocalDeclarations(indent, prefix, member.name, member.len) - # Single Struct - else: - # Update struct prefix - if first_level_param == True: - new_prefix = 'local_%s->' % member.name - decls += '%ssafe_%s *local_%s%s = NULL;\n' % (indent, member.type, prefix, member.name) - else: - new_prefix = '%s%s->' % (prefix, member.name) - # Declare safe_VarType for struct - pre_code += '%s if (%s%s) {\n' % (indent, prefix, member.name) - indent = self.incIndent(indent) - if first_level_param == True: - pre_code += '%s local_%s%s = new safe_%s(%s);\n' % (indent, prefix, member.name, member.type, member.name) - # Process sub-structs in this struct - (tmp_decl, tmp_pre, tmp_post) = self.uniquify_members(struct_info, indent, new_prefix, array_index, create_func, destroy_func, destroy_array, False) - decls += tmp_decl - pre_code += tmp_pre - post_code += tmp_post - indent = self.decIndent(indent) - pre_code += '%s }\n' % indent - if first_level_param == True: - post_code += self.cleanUpLocalDeclarations(indent, prefix, member.name, member.len) - return decls, pre_code, post_code - # - # For a particular API, generate the non-dispatchable-object wrapping/unwrapping code - def generate_wrapping_code(self, cmd): - indent = ' ' - proto = cmd.find('proto/name') - params = cmd.findall('param') - if proto.text is not None: - cmd_member_dict = dict(self.cmdMembers) - cmd_info = cmd_member_dict[proto.text] - # Handle ndo create/allocate operations - if cmd_info[0].iscreate: - create_ndo_code = self.generate_create_ndo_code(indent, proto, params, cmd_info) - else: - create_ndo_code = '' - # Handle ndo destroy/free operations - if cmd_info[0].isdestroy: - (destroy_array, destroy_ndo_code) = self.generate_destroy_ndo_code(indent, proto, cmd_info) - else: - destroy_array = False - destroy_ndo_code = '' - paramdecl = '' - param_pre_code = '' - param_post_code = '' - create_func = True if create_ndo_code else False - destroy_func = True if destroy_ndo_code else False - (paramdecl, param_pre_code, param_post_code) = self.uniquify_members(cmd_info, indent, '', 0, create_func, destroy_func, destroy_array, True) - param_post_code += create_ndo_code - if destroy_ndo_code: - if destroy_array == True: - param_post_code += destroy_ndo_code - else: - param_pre_code += destroy_ndo_code - if param_pre_code: - if (not destroy_func) or (destroy_array): - param_pre_code = '%s{\n%s%s%s%s}\n' % (' ', indent, self.lock_guard(indent), param_pre_code, indent) - return paramdecl, param_pre_code, param_post_code - # - # Capture command parameter info needed to wrap NDOs as well as handling some boilerplate code - def genCmd(self, cmdinfo, cmdname): - if cmdname in self.interface_functions: - return - if cmdname in self.no_autogen_list: - decls = self.makeCDecls(cmdinfo.elem) - self.appendSection('command', '') - self.appendSection('command', '// Declare only') - self.appendSection('command', decls[0]) - self.intercepts += [ ' {"%s", reinterpret_cast(%s)},' % (cmdname,cmdname[2:]) ] - return - # Add struct-member type information to command parameter information - OutputGenerator.genCmd(self, cmdinfo, cmdname) - members = cmdinfo.elem.findall('.//param') - # 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) - struct_member_dict = dict(self.structMembers) - # Generate member info - membersInfo = [] - for member in members: - # Get type and name of member - info = self.getTypeNameTuple(member) - type = info[0] - name = info[1] - cdecl = self.makeCParamDecl(member, 0) - # Check for parameter name in lens set - iscount = True if name in lens else False - len = self.getLen(member) - isconst = True if 'const' in cdecl else False - ispointer = self.paramIsPointer(member) - # Mark param as local if it is an array of NDOs - islocal = False; - if self.isHandleTypeNonDispatchable(type) == True: - if (len is not None) and (isconst == True): - islocal = True - # Or if it's a struct that contains an NDO - elif type in struct_member_dict: - if self.struct_contains_ndo(type) == True: - islocal = True - - isdestroy = True if True in [destroy_txt in cmdname for destroy_txt in ['Destroy', 'Free']] else False - iscreate = True if True in [create_txt in cmdname for create_txt in ['Create', 'Allocate']] else False - - membersInfo.append(self.CommandParam(type=type, - name=name, - ispointer=ispointer, - isconst=isconst, - iscount=iscount, - len=len, - extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None, - cdecl=cdecl, - islocal=islocal, - iscreate=iscreate, - isdestroy=isdestroy)) - self.cmdMembers.append(self.CmdMemberData(name=cmdname, members=membersInfo)) - # Generate NDO wrapping/unwrapping code for all parameters - (api_decls, api_pre, api_post) = self.generate_wrapping_code(cmdinfo.elem) - # If API doesn't contain an NDO's, don't fool with it - if not api_decls and not api_pre and not api_post: - return - # Record that the function will be intercepted - if (self.featureExtraProtect != None): - self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ] - self.intercepts += [ ' {"%s", reinterpret_cast(%s)},' % (cmdname,cmdname[2:]) ] - if (self.featureExtraProtect != None): - self.intercepts += [ '#endif' ] - decls = self.makeCDecls(cmdinfo.elem) - self.appendSection('command', '') - self.appendSection('command', decls[0][:-1]) - self.appendSection('command', '{') - # Setup common to call wrappers, first parameter is always dispatchable - dispatchable_type = cmdinfo.elem.find('param/type').text - dispatchable_name = cmdinfo.elem.find('param/name').text - # Generate local instance/pdev/device data lookup - self.appendSection('command', ' layer_data *dev_data = get_my_data_ptr(get_dispatch_key('+dispatchable_name+'), layer_data_map);') - # Handle return values, if any - resulttype = cmdinfo.elem.find('proto/type') - if (resulttype != None and resulttype.text == 'void'): - resulttype = None - if (resulttype != None): - assignresult = resulttype.text + ' result = ' - else: - assignresult = '' - # Pre-pend declarations and pre-api-call codegen - if api_decls: - self.appendSection('command', "\n".join(str(api_decls).rstrip().split("\n"))) - if api_pre: - self.appendSection('command', "\n".join(str(api_pre).rstrip().split("\n"))) - # Generate the API call itself - # Gather the parameter items - params = cmdinfo.elem.findall('param/name') - # Pull out the text for each of the parameters, separate them by commas in a list - paramstext = ', '.join([str(param.text) for param in params]) - # If any of these paramters has been replaced by a local var, fix up the list - cmd_member_dict = dict(self.cmdMembers) - params = cmd_member_dict[cmdname] - for param in params: - if param.islocal == True: - if param.ispointer == True: - paramstext = paramstext.replace(param.name, '(%s %s*)local_%s' % ('const', param.type, param.name)) - else: - paramstext = paramstext.replace(param.name, '(%s %s)local_%s' % ('const', param.type, param.name)) - # Use correct dispatch table - if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]: - API = cmdinfo.elem.attrib.get('name').replace('vk','dev_data->instance_dispatch_table->',1) - else: - API = cmdinfo.elem.attrib.get('name').replace('vk','dev_data->device_dispatch_table->',1) - # Put all this together for the final down-chain call - self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');') - # And add the post-API-call codegen - self.appendSection('command', "\n".join(str(api_post).rstrip().split("\n"))) - # Handle the return result variable, if any - if (resulttype != None): - self.appendSection('command', ' return result;') - self.appendSection('command', '}') -- cgit v1.2.3