summaryrefslogtreecommitdiff
path: root/external/vulkan/registry/generator.py
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2020-08-21 16:04:42 -0400
committerGitHub <noreply@github.com>2020-08-21 13:04:42 -0700
commitfcac02e405661de311b5ceebbd6d3e2c78bf8aea (patch)
tree6e79865b39f0739d2ac9c3f91cc4129c244b6977 /external/vulkan/registry/generator.py
parent49067fd2e97b40649df3fa2ce096f78c2e45da5a (diff)
Vulkan update/NVAPI support (#1511)
* First pass at incorporating nvapi into test harness. * D3d12 Atomic Float Add via NVAPI working * Dx12 atomic float appears to work. * Atomic float add on Dx12. * Added atomic64 feature addition to vk. Fix correct output for atomic-float-byte-address.slang * Disable atomic float failing tests. * Upgraded VK headers. * Detect atomic float availability on VK. * Try to get test working for in64 atomic. * Made HLSL prelude controlled via the render-test requirements. * Added -enable-nvapi to premake. * Fix D3D12Renderer when NVAPI is not available. * Small improvements to VKRenderer. * Improve atomic documentation in target-compatibility.md.
Diffstat (limited to 'external/vulkan/registry/generator.py')
-rw-r--r--external/vulkan/registry/generator.py1105
1 files changed, 765 insertions, 340 deletions
diff --git a/external/vulkan/registry/generator.py b/external/vulkan/registry/generator.py
index fbd4f8d4c..c6e58720e 100644
--- a/external/vulkan/registry/generator.py
+++ b/external/vulkan/registry/generator.py
@@ -1,216 +1,259 @@
#!/usr/bin/python3 -i
#
-# Copyright (c) 2013-2019 The Khronos Group Inc.
+# Copyright (c) 2013-2020 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.
+# SPDX-License-Identifier: Apache-2.0
+"""Base class for source/header/doc generators, as well as some utility functions."""
from __future__ import unicode_literals
-import io,os,re,sys,pdb
-def write( *args, **kwargs ):
- file = kwargs.pop('file',sys.stdout)
- end = kwargs.pop('end','\n')
- file.write(' '.join([str(arg) for arg in args]))
+import io
+import os
+import pdb
+import re
+import shutil
+import sys
+import tempfile
+try:
+ from pathlib import Path
+except ImportError:
+ from pathlib2 import Path
+
+from spec_tools.util import getElemName, getElemType
+
+
+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 <feature> tag)
-# ARB/KHR/OES (Khronos extensions)
-# other (EXT/vendor extensions)
-# This will need changing for Vulkan!
+def noneStr(s):
+ """Return string argument, or "" if argument is None.
+
+ Used in converting etree Elements into text.
+ s - string to convert"""
+ if s:
+ return s
+ return ""
+
+
+def enquote(s):
+ """Return string argument with surrounding quotes,
+ for serialization into Python code."""
+ if s:
+ return "'{}'".format(s)
+ return None
+
+
def regSortCategoryKey(feature):
- if (feature.elem.tag == 'feature'):
+ """Sort key for regSortFeatures.
+ Sorts by category of the feature name string:
+
+ - Core API features (those defined with a `<feature>` tag)
+ - ARB/KHR/OES (Khronos extensions)
+ - other (EXT/vendor extensions)"""
+
+ if feature.elem.tag == 'feature':
return 0
- elif (feature.category == 'ARB' or
- feature.category == 'KHR' or
- feature.category == 'OES'):
+ if (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
+ return 2
+
+
+def regSortOrderKey(feature):
+ """Sort key for regSortFeatures - key is the sortorder attribute."""
+
+ # print("regSortOrderKey {} -> {}".format(feature.name, feature.sortorder))
+ return feature.sortorder
+
-# Second sort key for regSortFeatures.
-# Sorts by feature version. <extension> elements all have version number "0"
def regSortFeatureVersionKey(feature):
+ """Sort key for regSortFeatures - key is the feature version.
+ `<extension>` elements all have version number 0."""
+
return float(feature.versionNumber)
-# Tertiary sort key for regSortFeatures.
-# Sorts by extension number. <feature> elements all have extension number 0.
+
def regSortExtensionNumberKey(feature):
+ """Sort key for regSortFeatures - key is the extension number.
+ `<feature>` elements all have extension number 0."""
+
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)
+ """Default sort procedure for features.
+
+ - Sorts by explicit sort order (default 0) relative to other features
+ - then by feature category ('feature' or 'extension'),
+ - then by version number (for features)
+ - then by extension number (for extensions)"""
+ featureList.sort(key=regSortExtensionNumberKey)
+ featureList.sort(key=regSortFeatureVersionKey)
+ featureList.sort(key=regSortCategoryKey)
+ featureList.sort(key=regSortOrderKey)
+
-# 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 <api> '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 <extension>. 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.
-# emitExtensions - regex matching names of extensions to actually emit
-# interfaces for (though all requested versions are considered when
-# deciding which interfaces to generate).
-# 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"""
+ """Base class for options used during header/documentation production.
+
+ These options are target language independent, and used by
+ Registry.apiGen() and by base OutputGenerator objects."""
+
def __init__(self,
- filename = None,
- directory = '.',
- apiname = None,
- profile = None,
- versions = '.*',
- emitversions = '.*',
- defaultExtensions = None,
- addExtensions = None,
- removeExtensions = None,
- emitExtensions = 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)
+ conventions=None,
+ filename=None,
+ directory='.',
+ genpath=None,
+ apiname=None,
+ profile=None,
+ versions='.*',
+ emitversions='.*',
+ defaultExtensions=None,
+ addExtensions=None,
+ removeExtensions=None,
+ emitExtensions=None,
+ reparentEnums=True,
+ sortProcedure=regSortFeatures):
+ """Constructor.
+
+ Arguments:
+
+ - conventions - may be mandatory for some generators:
+ an object that implements ConventionsBase
+ - filename - basename of file to generate, or None to write to stdout.
+ - directory - directory in which to generate files
+ - genpath - path to previously generated files, such as api.py
+ - apiname - string matching `<api>` '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 `<extension>`. 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.
+ - emitExtensions - regex matching names of extensions to actually emit
+ interfaces for (though all requested versions are considered when
+ deciding which interfaces to generate).
+ - reparentEnums - move <enum> elements which extend an enumerated
+ type from <feature> or <extension> elements to the target <enums>
+ element. This is required for almost all purposes, but the
+ InterfaceGenerator relies on the list of interfaces in the <feature>
+ or <extension> being complete. Defaults to True.
+ - 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, by core API version number or extension number in each
+ group.
+
+ The regex patterns can be None or empty, in which case they match
+ nothing."""
+ self.conventions = conventions
+ """may be mandatory for some generators:
+ an object that implements ConventionsBase"""
+
+ self.filename = filename
+ "basename of file to generate, or None to write to stdout."
+
+ self.genpath = genpath
+ """path to previously generated files, such as api.py"""
+
+ self.directory = directory
+ "directory in which to generate filename"
+
+ self.apiname = apiname
+ "string matching `<api>` 'apiname' attribute, e.g. 'gl'."
+
+ self.profile = profile
+ "string specifying API profile , e.g. 'core', or None."
+
+ self.versions = self.emptyRegex(versions)
+ """regex matching API versions to process interfaces for.
+ Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions."""
+
+ self.emitversions = self.emptyRegex(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]'`."""
+
self.defaultExtensions = defaultExtensions
- self.addExtensions = self.emptyRegex(addExtensions)
- self.removeExtensions = self.emptyRegex(removeExtensions)
- self.emitExtensions = self.emptyRegex(emitExtensions)
- 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 == ''):
+ """If not None, a string which must in its
+ entirety match the pattern in the "supported" attribute of
+ the `<extension>`. Defaults to None. Usually the same as apiname."""
+
+ self.addExtensions = self.emptyRegex(addExtensions)
+ """regex matching names of additional extensions
+ to include. Defaults to None."""
+
+ self.removeExtensions = self.emptyRegex(removeExtensions)
+ """regex matching names of extensions to
+ remove (after defaultExtensions and addExtensions). Defaults
+ to None."""
+
+ self.emitExtensions = self.emptyRegex(emitExtensions)
+ """regex matching names of extensions to actually emit
+ interfaces for (though all requested versions are considered when
+ deciding which interfaces to generate)."""
+
+ self.reparentEnums = reparentEnums
+ """boolean specifying whether to remove <enum> elements from
+ <feature> or <extension> when extending an <enums> type."""
+
+ self.sortProcedure = 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."""
+
+ self.codeGenerator = False
+ """True if this generator makes compilable code"""
+
+ def emptyRegex(self, pat):
+ """Substitute a regular expression which matches no version
+ or extension names for None or the empty string."""
+ if not 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 <version> / <extension> to generate
-# emit - actually write to the header only when True
-# endFeature() - finish an interface.
-# genType(typeinfo,name,alias) - generate interface for a type
-# typeinfo - TypeInfo for a type
-# genStruct(typeinfo,name,alias) - generate interface for a C "struct" type.
-# typeinfo - TypeInfo for a type interpreted as a struct
-# genGroup(groupinfo,name,alias) - generate interface for a group of enums (C "enum")
-# groupinfo - GroupInfo for a group
-# genEnum(enuminfo,name,alias) - generate interface for an enum (constant)
-# enuminfo - EnumInfo for an enum
-# name - enum name
-# genCmd(cmdinfo,name,alias) - generate interface for a command
-# cmdinfo - CmdInfo for a command
-# isEnumRequired(enumElem) - return True if this <enum> element is required
-# elem - <enum> element to test
-# makeCDecls(cmd) - return C prototype and function pointer typedef for a
-# <command> Element, as a list of two strings
-# cmd - Element for the <command>
-# newline() - print a newline to the output file (utility function)
-#
+ return pat
+
+
class OutputGenerator:
- """Generate specified API interfaces in a specific style, such as a C header"""
- #
+ """Generate specified API interfaces in a specific style, such as a C header.
+
+ Base class for generating API interfaces.
+ Manages basic logic, logging, and output file control.
+ Derived classes actually generate formatted output.
+ """
+
# categoryToPath - map XML 'category' to include file directory name
categoryToPath = {
- 'bitmask' : 'flags',
- 'enum' : 'enums',
- 'funcpointer' : 'funcpointers',
- 'handle' : 'handles',
- 'define' : 'defines',
- 'basetype' : 'basetypes',
+ '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):
+
+ def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout):
+ """Constructor
+
+ - errFile, warnFile, diagFile - file handles to write errors,
+ warnings, diagnostics to. May be None to not write."""
self.outFile = None
self.errFile = errFile
self.warnFile = warnFile
@@ -219,93 +262,105 @@ class OutputGenerator:
self.featureName = None
self.genOpts = None
self.registry = None
+ self.featureDictionary = {}
# Used for extension enum value generation
- self.extBase = 1000000000
+ 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
+
+ # API dictionary, which may be loaded by the beginFile method of
+ # derived generators.
+ self.apidict = None
+
def logMsg(self, level, *args):
- """Log a message at the given level. Can be ignored or log to a file"""
- if (level == 'error'):
+ """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"""
+ if level == 'error':
strfile = io.StringIO()
write('ERROR:', *args, file=strfile)
- if (self.errFile != None):
+ if self.errFile is not None:
write(strfile.getvalue(), file=self.errFile)
raise UserWarning(strfile.getvalue())
- elif (level == 'warn'):
- if (self.warnFile != None):
+ elif level == 'warn':
+ if self.warnFile is not None:
write('WARNING:', *args, file=self.warnFile)
- elif (level == 'diag'):
- if (self.diagFile != None):
+ elif level == 'diag':
+ if self.diagFile is not None:
write('DIAG:', *args, file=self.diagFile)
else:
raise UserWarning(
'*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
- #
- # enumToValue - parses and converts an <enum> 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.
- # A 'alias' attribute contains the name of another enum
- # which this is an alias of. The other enum must be
- # declared first when emitting this enum.
+
def enumToValue(self, elem, needsNum):
+ """Parse and convert an `<enum>` 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.
+ - An '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
+ API extension enum scheme in this function.
+ - An 'alias' attribute contains the name of another enum
+ which this is an alias of. The other enum must be
+ declared first when emitting this enum."""
name = elem.get('name')
numVal = None
- if ('value' in elem.keys()):
+ if 'value' in elem.keys():
value = elem.get('value')
# print('About to translate value =', value, 'type =', type(value))
- if (needsNum):
+ 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'):
+ # if t is not 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()):
+ if 'bitpos' in elem.keys():
value = elem.get('bitpos')
- numVal = int(value, 0)
- numVal = 1 << numVal
+ bitpos = int(value, 0)
+ numVal = 1 << bitpos
value = '0x%08x' % numVal
+ if bitpos >= 32:
+ value = value + 'ULL'
self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
return [numVal, value]
- if ('offset' in elem.keys()):
+ 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)
+ offset = int(elem.get('offset'), 0)
+ extnumber = int(elem.get('extnumber'), 0)
extends = elem.get('extends')
- if ('dir' in elem.keys()):
+ if 'dir' in elem.keys():
enumNegative = True
self.logMsg('diag', 'Enum', name, 'offset =', offset,
- 'extnumber =', extnumber, 'extends =', extends,
- 'enumNegative =', enumNegative)
+ '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
+ if enumNegative:
+ numVal *= -1
value = '%d' % numVal
# More logic needed!
self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
@@ -313,11 +368,13 @@ class OutputGenerator:
if 'alias' in elem.keys():
return [None, elem.get('alias')]
return [None, None]
- #
- # checkDuplicateEnums - sanity check for enumerated values
- # enums - list of <enum> Elements
- # returns the list with duplicates stripped
+
def checkDuplicateEnums(self, enums):
+ """Check enumerated values for duplicates.
+
+ - enums - list of `<enum>` Elements
+
+ returns the list with duplicates stripped"""
# Dictionaries indexed by name and numeric value.
# Entries are [ Element, numVal, strVal ] matching name or value
@@ -336,15 +393,15 @@ class OutputGenerator:
# Duplicate enum values for the same name are benign. This
# happens when defining the same enum conditionally in
# several extension blocks.
- if (strVal2 == strVal or (numVal != None and
- numVal == numVal2)):
+ if (strVal2 == strVal or (numVal is not None
+ and numVal == numVal2)):
True
# self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name +
# ') found with the same value:' + strVal)
else:
- self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name +
- ') found with different values:' + strVal +
- ' and ' + strVal2)
+ self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name
+ + ') found with different values:' + strVal
+ + ' and ' + strVal2)
# Don't add the duplicate to the returned list
continue
@@ -353,74 +410,319 @@ class OutputGenerator:
# still add this enum to the list.
(name2, numVal2, strVal2) = valueMap[numVal]
- try:
- self.logMsg('warn', 'Two enums found with the same value: '
- + name + ' = ' + name2.get('name') + ' = ' + strVal)
- except:
- pdb.set_trace()
+ msg = 'Two enums found with the same value: {} = {} = {}'.format(
+ name, name2.get('name'), strVal)
+ self.logMsg('error', msg)
# Track this enum to detect followon duplicates
- nameMap[name] = [ elem, numVal, strVal ]
- if numVal != None:
- valueMap[numVal] = [ elem, numVal, strVal ]
+ nameMap[name] = [elem, numVal, strVal]
+ if numVal is not None:
+ valueMap[numVal] = [elem, numVal, strVal]
# Add this enum to the list
stripped.append(elem)
# Return the list
return stripped
- #
+
+ def buildEnumCDecl(self, expand, groupinfo, groupName):
+ """Generate the C declaration for an enum"""
+ groupElem = groupinfo.elem
+
+ # Determine the required bit width for the enum group.
+ # 32 is the default, which generates C enum types for the values.
+ bitwidth = 32
+
+ # If the constFlagBits preference is set, 64 is the default for bitmasks
+ if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask':
+ bitwidth = 64
+
+ # Check for an explicitly defined bitwidth, which will override any defaults.
+ if groupElem.get('bitwidth'):
+ try:
+ bitwidth = int(groupElem.get('bitwidth'))
+ except ValueError as ve:
+ self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n')
+ exit(1)
+
+ # Bitmask types support 64-bit flags, so have different handling
+ if groupElem.get('type') == 'bitmask':
+
+ # Validate the bitwidth and generate values appropriately
+ # Bitmask flags up to 64-bit are generated as static const uint64_t values
+ # Bitmask flags up to 32-bit are generated as C enum values
+ if bitwidth > 64:
+ self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n')
+ exit(1)
+ elif bitwidth > 32:
+ return self.buildEnumCDecl_Bitmask(groupinfo, groupName)
+ else:
+ return self.buildEnumCDecl_Enum(expand, groupinfo, groupName)
+ else:
+ # Validate the bitwidth and generate values appropriately
+ # Enum group types up to 32-bit are generated as C enum values
+ if bitwidth > 32:
+ self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n')
+ exit(1)
+ else:
+ return self.buildEnumCDecl_Enum(expand, groupinfo, groupName)
+
+ def buildEnumCDecl_Bitmask(self, groupinfo, groupName):
+ """Generate the C declaration for an "enum" that is actually a
+ set of flag bits"""
+ groupElem = groupinfo.elem
+ flagTypeName = groupinfo.flagType.elem.get('name')
+
+ # Prefix
+ body = "// Flag bits for " + flagTypeName + "\n"
+
+ # Maximum allowable value for a flag (unsigned 64-bit integer)
+ maxValidValue = 2**(64) - 1
+ minValidValue = 0
+
+ # Loop over the nested 'enum' tags.
+ for elem in groupElem.findall('enum'):
+ # Convert the value to an integer and use that to track min/max.
+ # Values of form -(number) are accepted but nothing more complex.
+ # Should catch exceptions here for more complex constructs. Not yet.
+ (numVal, strVal) = self.enumToValue(elem, True)
+ name = elem.get('name')
+
+ # Range check for the enum value
+ if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
+ self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n')
+ exit(1)
+
+ body += self.genRequirements(name, mustBeFound = False)
+ body += "static const {} {} = {};\n".format(flagTypeName, name, strVal)
+
+ # Postfix
+
+ return ("bitmask", body)
+
+ def buildEnumCDecl_Enum(self, expand, groupinfo, groupName):
+ """Generate the C declaration for an enumerated type"""
+ groupElem = groupinfo.elem
+
+ # Break the group name into prefix and suffix portions for range
+ # enum generation
+ expandName = re.sub(r'([0-9a-z_])([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]
+
+ # Prefix
+ body = ["typedef enum %s {" % groupName]
+
+ # @@ Should use the type="bitmask" attribute instead
+ isEnum = ('FLAG_BITS' not in expandPrefix)
+
+ # Allowable range for a C enum - which is that of a signed 32-bit integer
+ maxValidValue = 2**(32 - 1) - 1
+ minValidValue = (maxValidValue * -1) - 1
+
+
+ # Get a list of nested 'enum' tags.
+ enums = groupElem.findall('enum')
+
+ # Check for and report duplicates, and return a list with them
+ # removed.
+ enums = self.checkDuplicateEnums(enums)
+
+ # Loop over the nested 'enum' tags. Keep track of the minimum and
+ # maximum numeric values, if they can be determined; but only for
+ # core API enumerants, not extension enumerants. This is inferred
+ # by looking for 'extends' attributes.
+ minName = None
+
+ # Accumulate non-numeric enumerant values separately and append
+ # them following the numeric values, to allow for aliases.
+ # NOTE: this doesn't do a topological sort yet, so aliases of
+ # aliases can still get in the wrong order.
+ aliasText = []
+
+ for elem in enums:
+ # Convert the value to an integer and use that to track min/max.
+ # Values of form -(number) are accepted but nothing more complex.
+ # Should catch exceptions here for more complex constructs. Not yet.
+ (numVal, strVal) = self.enumToValue(elem, True)
+ name = elem.get('name')
+
+ # Extension enumerants are only included if they are required
+ if self.isEnumRequired(elem):
+ # Indent requirements comment, if there is one
+ decl = self.genRequirements(name, mustBeFound = False)
+ if decl != '':
+ decl = ' ' + decl
+ decl += " {} = {},".format(name, strVal)
+ if numVal is not None:
+ body.append(decl)
+ else:
+ aliasText.append(decl)
+
+ # Range check for the enum value
+ if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
+ self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n')
+ exit(1)
+
+
+ # Don't track min/max for non-numbers (numVal is None)
+ if isEnum and numVal is not None and elem.get('extends') is None:
+ if minName is None:
+ minName = maxName = name
+ minValue = maxValue = numVal
+ elif numVal < minValue:
+ minName = name
+ minValue = numVal
+ elif numVal > maxValue:
+ maxName = name
+ maxValue = numVal
+
+ # Now append the non-numeric enumerant values
+ body.extend(aliasText)
+
+ # Generate min/max value tokens - legacy use case.
+ if isEnum and expand:
+ body.extend((" {}_BEGIN_RANGE{} = {},".format(expandPrefix, expandSuffix, minName),
+ " {}_END_RANGE{} = {},".format(
+ expandPrefix, expandSuffix, maxName),
+ " {}_RANGE_SIZE{} = ({} - {} + 1),".format(expandPrefix, expandSuffix, maxName, minName)))
+
+ # Generate a range-padding value to ensure the enum is 32 bits, but
+ # only in code generators, so it doesn't appear in documentation
+ if (self.genOpts.codeGenerator or
+ self.conventions.generate_max_enum_in_docs):
+ body.append(" {}_MAX_ENUM{} = 0x7FFFFFFF".format(
+ expandPrefix, expandSuffix))
+
+ # Postfix
+ body.append("} %s;" % groupName)
+
+ # Determine appropriate section for this declaration
+ if groupElem.get('type') == 'bitmask':
+ section = 'bitmask'
+ else:
+ section = 'group'
+
+ return (section, '\n'.join(body))
+
def makeDir(self, path):
+ """Create a directory, if not already done.
+
+ Generally called from derived generators creating hierarchies."""
self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
- if not (path in self.madeDirs.keys()):
+ if path not in self.madeDirs:
# 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):
+ """Start a new interface file
+
+ - genOpts - GeneratorOptions controlling what's generated and how"""
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):
- filename = self.genOpts.directory + '/' + self.genOpts.filename
- self.outFile = io.open(filename, 'w', encoding='utf-8')
+ self.should_insert_may_alias_macro = \
+ self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts)
+
+ # Try to import the API dictionary, api.py, if it exists. Nothing in
+ # api.py cannot be extracted directly from the XML, and in the
+ # future we should do that.
+ if self.genOpts.genpath is not None:
+ try:
+ sys.path.insert(0, self.genOpts.genpath)
+ import api
+ self.apidict = api
+ except ImportError:
+ self.apidict = None
+
+ self.conventions = genOpts.conventions
+
+ # Open a temporary file for accumulating output.
+ if self.genOpts.filename is not None:
+ self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False)
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()
+ if self.errFile:
+ self.errFile.flush()
+ if self.warnFile:
+ self.warnFile.flush()
+ if self.diagFile:
+ self.diagFile.flush()
self.outFile.flush()
- if (self.outFile != sys.stdout and self.outFile != sys.stderr):
+ if self.outFile != sys.stdout and self.outFile != sys.stderr:
self.outFile.close()
+
+ # On successfully generating output, move the temporary file to the
+ # target file.
+ if self.genOpts.filename is not None:
+ if sys.platform == 'win32':
+ directory = Path(self.genOpts.directory)
+ if not Path.exists(directory):
+ os.makedirs(directory)
+ shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename)
+ os.remove(self.outFile.name)
self.genOpts = None
- #
+
def beginFeature(self, interface, emit):
+ """Write interface for a feature and tag generated features as having been done.
+
+ - interface - element for the `<version>` / `<extension>` to generate
+ - emit - actually write to the header only when True"""
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
+ """Finish an interface file, closing it when done.
+
+ Derived classes responsible for emitting feature"""
self.featureName = None
self.featureExtraProtect = None
- # Utility method to validate we're generating something only inside a
- # <feature> tag
+
+ def genRequirements(self, name, mustBeFound = True):
+ """Generate text showing what core versions and extensions introduce
+ an API. This exists in the base Generator class because it's used by
+ the shared enumerant-generating interfaces (buildEnumCDecl, etc.).
+ Here it returns an empty string for most generators, but can be
+ overridden by e.g. DocGenerator.
+
+ - name - name of the API
+ - mustBeFound - If True, when requirements for 'name' cannot be
+ determined, a warning comment is generated.
+ """
+
+ return ''
+
def validateFeature(self, featureType, featureName):
- if (self.featureName == None):
+ """Validate we're generating something only inside a `<feature>` tag"""
+ if self.featureName is None:
raise UserWarning('Attempt to generate', featureType,
- featureName, 'when not in feature')
- #
- # Type generation
+ featureName, 'when not in feature')
+
def genType(self, typeinfo, name, alias):
+ """Generate interface for a type
+
+ - typeinfo - TypeInfo for a type
+
+ Extend to generate as desired in your derived class."""
self.validateFeature('type', name)
- #
- # Struct (e.g. C "struct" type) generation
- def genStruct(self, typeinfo, name, alias):
- self.validateFeature('struct', name)
+
+ def genStruct(self, typeinfo, typeName, alias):
+ """Generate interface for a C "struct" type.
+
+ - typeinfo - TypeInfo for a type interpreted as a struct
+
+ Extend to generate as desired in your derived class."""
+ self.validateFeature('struct', typeName)
# The mixed-mode <member> tags may contain no-op <comment> tags.
# It is convenient to remove them here where all output generators
@@ -428,40 +730,63 @@ class OutputGenerator:
for member in typeinfo.elem.findall('.//member'):
for comment in member.findall('comment'):
member.remove(comment)
- #
- # Group (e.g. C "enum" type) generation
- def genGroup(self, groupinfo, name, alias):
- self.validateFeature('group', name)
- #
- # Enumerant (really, constant) generation
- def genEnum(self, enuminfo, name, alias):
- self.validateFeature('enum', name)
- #
- # Command generation
- def genCmd(self, cmd, name, alias):
- self.validateFeature('command', name)
- #
- # Utility functions - turn a <proto> <name> into C-language prototype
- # and typedef declarations for that name.
- # name - contents of <name> tag
- # tail - whatever text follows that tag in the Element
+
+ def genGroup(self, groupinfo, groupName, alias):
+ """Generate interface for a group of enums (C "enum")
+
+ - groupinfo - GroupInfo for a group.
+
+ Extend to generate as desired in your derived class."""
+
+ self.validateFeature('group', groupName)
+
+ def genEnum(self, enuminfo, typeName, alias):
+ """Generate interface for an enum (constant).
+
+ - enuminfo - EnumInfo for an enum
+ - name - enum name
+
+ Extend to generate as desired in your derived class."""
+ self.validateFeature('enum', typeName)
+
+ def genCmd(self, cmd, cmdinfo, alias):
+ """Generate interface for a command.
+
+ - cmdinfo - CmdInfo for a command
+
+ Extend to generate as desired in your derived class."""
+ self.validateFeature('command', cmdinfo)
+
def makeProtoName(self, name, tail):
+ """Turn a `<proto>` `<name>` into C-language prototype
+ and typedef declarations for that name.
+
+ - name - contents of `<name>` tag
+ - tail - whatever text follows that tag in the Element"""
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 <param> or <member> block (e.g. function parameter
- # or structure/union member).
- # param - Element (<param> or <member>) to format
- # aligncol - if non-zero, attempt to align the nested <name> element
- # at this column
+ """Make the function-pointer typedef name for a command."""
+ return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
+
def makeCParamDecl(self, param, aligncol):
- paramdecl = ' ' + noneStr(param.text)
+ """Return a string which is an indented, formatted
+ declaration for a `<param>` or `<member>` block (e.g. function parameter
+ or structure/union member).
+
+ - param - Element (`<param>` or `<member>`) to format
+ - aligncol - if non-zero, attempt to align the nested `<name>` element
+ at this column"""
+ indent = ' '
+ paramdecl = indent + noneStr(param.text)
for elem in param:
text = noneStr(elem.text)
tail = noneStr(elem.tail)
- if (elem.tag == 'name' and aligncol > 0):
+
+ if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
+ # OpenXR-specific macro insertion - but not in apiinc for the spec
+ tail = self.genOpts.conventions.make_voidpointer_alias(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()
@@ -469,39 +794,141 @@ class OutputGenerator:
# 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) + ' '
+ paramdecl = paramdecl.ljust(aligncol - 1) + ' '
newLen = len(paramdecl)
self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
paramdecl += text + tail
+ if aligncol == 0:
+ # Squeeze out multiple spaces other than the indentation
+ paramdecl = indent + ' '.join(paramdecl.split())
return paramdecl
- #
- # getCParamTypeLength - return the length of the type field is an indented, formatted
- # declaration for a <param> or <member> block (e.g. function parameter
- # or structure/union member).
- # param - Element (<param> or <member>) to identify
+
def getCParamTypeLength(self, param):
+ """Return the length of the type field is an indented, formatted
+ declaration for a `<param>` or `<member>` block (e.g. function parameter
+ or structure/union member).
+
+ - param - Element (`<param>` or `<member>`) to identify"""
+
+ # Allow for missing <name> tag
+ newLen = 0
paramdecl = ' ' + noneStr(param.text)
for elem in param:
text = noneStr(elem.text)
tail = noneStr(elem.tail)
- if (elem.tag == 'name'):
+
+ if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
+ # OpenXR-specific macro insertion
+ tail = self.genOpts.conventions.make_voidpointer_alias(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 <enum> element is
- # required, False otherwise
- # elem - <enum> element to test
+
+ def getMaxCParamTypeLength(self, info):
+ """Return the length of the longest type field for a member/parameter.
+
+ - info - TypeInfo or CommandInfo.
+ """
+ lengths = (self.getCParamTypeLength(member)
+ for member in info.getMembers())
+ return max(lengths)
+
+ def getHandleParent(self, typename):
+ """Get the parent of a handle object."""
+ info = self.registry.typedict.get(typename)
+ if info is None:
+ return None
+
+ elem = info.elem
+ if elem is not None:
+ return elem.get('parent')
+
+ return None
+
+ def iterateHandleAncestors(self, typename):
+ """Iterate through the ancestors of a handle type."""
+ current = self.getHandleParent(typename)
+ while current is not None:
+ yield current
+ current = self.getHandleParent(current)
+
+ def getHandleAncestors(self, typename):
+ """Get the ancestors of a handle object."""
+ return list(self.iterateHandleAncestors(typename))
+
+ def getTypeCategory(self, typename):
+ """Get the category of a type."""
+ info = self.registry.typedict.get(typename)
+ if info is None:
+ return None
+
+ elem = info.elem
+ if elem is not None:
+ return elem.get('category')
+ return None
+
+ def isStructAlwaysValid(self, structname):
+ """Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance)."""
+ # A conventions object is required for this call.
+ if not self.conventions:
+ raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.")
+
+ if self.conventions.type_always_valid(structname):
+ return True
+
+ category = self.getTypeCategory(structname)
+ if self.conventions.category_requires_validation(category):
+ return False
+
+ info = self.registry.typedict.get(structname)
+ assert(info is not None)
+
+ members = info.getMembers()
+
+ for member in members:
+ member_name = getElemName(member)
+ if member_name in (self.conventions.structtype_member_name,
+ self.conventions.nextpointer_member_name):
+ return False
+
+ if member.get('noautovalidity'):
+ return False
+
+ member_type = getElemType(member)
+
+ if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member):
+ return False
+
+ if self.conventions.type_always_valid(member_type):
+ continue
+
+ member_category = self.getTypeCategory(member_type)
+
+ if self.conventions.category_requires_validation(member_category):
+ return False
+
+ if member_category in ('struct', 'union'):
+ if self.isStructAlwaysValid(member_type) is False:
+ return False
+
+ return True
+
def isEnumRequired(self, elem):
- required = elem.get('required') != None
+ """Return True if this `<enum>` element is
+ required, False otherwise
+
+ - elem - `<enum>` element to test"""
+ required = elem.get('required') is not None
self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
- '->', required)
+ '->', required)
return required
- #@@@ This code is overridden by equivalent code now run in
- #@@@ Registry.generateFeature
+ # @@@ This code is overridden by equivalent code now run in
+ # @@@ Registry.generateFeature
required = False
@@ -520,18 +947,17 @@ class OutputGenerator:
return required
- #
- # makeCDecls - return C prototype and function pointer typedef for a
- # command, as a two-element list of strings.
- # cmd - Element containing a <command> tag
def makeCDecls(self, cmd):
- """Generate C function pointer typedef for <command> Element"""
+ """Return C prototype and function pointer typedef for a
+ `<command>` Element, as a two-element list of strings.
+
+ - cmd - Element containing a `<command>` tag"""
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 *<name>) around the name and
@@ -547,12 +973,18 @@ class OutputGenerator:
for elem in proto:
text = noneStr(elem.text)
tail = noneStr(elem.tail)
- if (elem.tag == 'name'):
+ if elem.tag == 'name':
pdecl += self.makeProtoName(text, tail)
tdecl += self.makeTypedefName(text, tail)
else:
pdecl += text + tail
tdecl += text + tail
+
+ if self.genOpts.alignFuncParam == 0:
+ # Squeeze out multiple spaces - there is no indentation
+ pdecl = ' '.join(pdecl.split())
+ tdecl = ' '.join(tdecl.split())
+
# Now add the parameter declaration list, which is identical
# for prototypes and typedefs. Concatenate all the text from
# a <param> node without the tags. No tree walking required
@@ -560,36 +992,29 @@ class OutputGenerator:
# 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
+ indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
+ for p in params)
+ indentdecl += ');'
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 += ', '
+ paramnames = (''.join(t for t in p.itertext())
+ for p in params)
+ paramdecl += ', '.join(paramnames)
else:
paramdecl += 'void'
- paramdecl += ");";
- return [ pdecl + indentdecl, tdecl + paramdecl ]
- #
+ paramdecl += ");"
+ return [pdecl + indentdecl, tdecl + paramdecl]
+
def newline(self):
+ """Print a newline to the output file (utility function)"""
write('', file=self.outFile)
def setRegistry(self, registry):
self.registry = registry
- #