Home | History | Annotate | Download | only in vulkan-validation-layers
      1 #!/usr/bin/python3 -i
      2 #
      3 # Copyright (c) 2013-2016 The Khronos Group Inc.
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #     http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 import os,re,sys
     18 
     19 def write( *args, **kwargs ):
     20     file = kwargs.pop('file',sys.stdout)
     21     end = kwargs.pop( 'end','\n')
     22     file.write( ' '.join([str(arg) for arg in args]) )
     23     file.write( end )
     24 
     25 # noneStr - returns string argument, or "" if argument is None.
     26 # Used in converting etree Elements into text.
     27 #   str - string to convert
     28 def noneStr(str):
     29     if (str):
     30         return str
     31     else:
     32         return ""
     33 
     34 # enquote - returns string argument with surrounding quotes,
     35 #   for serialization into Python code.
     36 def enquote(str):
     37     if (str):
     38         return "'" + str + "'"
     39     else:
     40         return None
     41 
     42 # apiName - returns True if name is a Vulkan name (vk/Vk/VK prefix, or a
     43 # function pointer type), False otherwise.
     44 def apiName(str):
     45     return str[0:2].lower() == 'vk' or str[0:3] == 'PFN'
     46 
     47 # Primary sort key for regSortFeatures.
     48 # Sorts by category of the feature name string:
     49 #   Core API features (those defined with a <feature> tag)
     50 #   ARB/KHR/OES (Khronos extensions)
     51 #   other       (EXT/vendor extensions)
     52 # This will need changing for Vulkan!
     53 def regSortCategoryKey(feature):
     54     if (feature.elem.tag == 'feature'):
     55         return 0
     56     elif (feature.category == 'ARB' or
     57           feature.category == 'KHR' or
     58           feature.category == 'OES'):
     59         return 1
     60     else:
     61         return 2
     62 
     63 # Secondary sort key for regSortFeatures.
     64 # Sorts by extension name.
     65 def regSortNameKey(feature):
     66     return feature.name
     67 
     68 # Second sort key for regSortFeatures.
     69 # Sorts by feature version. <extension> elements all have version number "0"
     70 def regSortFeatureVersionKey(feature):
     71     return float(feature.version)
     72 
     73 # Tertiary sort key for regSortFeatures.
     74 # Sorts by extension number. <feature> elements all have extension number 0.
     75 def regSortExtensionNumberKey(feature):
     76     return int(feature.number)
     77 
     78 # regSortFeatures - default sort procedure for features.
     79 # Sorts by primary key of feature category ('feature' or 'extension')
     80 #   then by version number (for features)
     81 #   then by extension number (for extensions)
     82 def regSortFeatures(featureList):
     83     featureList.sort(key = regSortExtensionNumberKey)
     84     featureList.sort(key = regSortFeatureVersionKey)
     85     featureList.sort(key = regSortCategoryKey)
     86 
     87 # GeneratorOptions - base class for options used during header production
     88 # These options are target language independent, and used by
     89 # Registry.apiGen() and by base OutputGenerator objects.
     90 #
     91 # Members
     92 #   filename - basename of file to generate, or None to write to stdout.
     93 #   directory - directory in which to generate filename
     94 #   apiname - string matching <api> 'apiname' attribute, e.g. 'gl'.
     95 #   profile - string specifying API profile , e.g. 'core', or None.
     96 #   versions - regex matching API versions to process interfaces for.
     97 #     Normally '.*' or '[0-9]\.[0-9]' to match all defined versions.
     98 #   emitversions - regex matching API versions to actually emit
     99 #    interfaces for (though all requested versions are considered
    100 #    when deciding which interfaces to generate). For GL 4.3 glext.h,
    101 #     this might be '1\.[2-5]|[2-4]\.[0-9]'.
    102 #   defaultExtensions - If not None, a string which must in its
    103 #     entirety match the pattern in the "supported" attribute of
    104 #     the <extension>. Defaults to None. Usually the same as apiname.
    105 #   addExtensions - regex matching names of additional extensions
    106 #     to include. Defaults to None.
    107 #   removeExtensions - regex matching names of extensions to
    108 #     remove (after defaultExtensions and addExtensions). Defaults
    109 #     to None.
    110 #   sortProcedure - takes a list of FeatureInfo objects and sorts
    111 #     them in place to a preferred order in the generated output.
    112 #     Default is core API versions, ARB/KHR/OES extensions, all
    113 #     other extensions, alphabetically within each group.
    114 # The regex patterns can be None or empty, in which case they match
    115 #   nothing.
    116 class GeneratorOptions:
    117     """Represents options during header production from an API registry"""
    118     def __init__(self,
    119                  filename = None,
    120                  directory = '.',
    121                  apiname = None,
    122                  profile = None,
    123                  versions = '.*',
    124                  emitversions = '.*',
    125                  defaultExtensions = None,
    126                  addExtensions = None,
    127                  removeExtensions = None,
    128                  sortProcedure = regSortFeatures):
    129         self.filename          = filename
    130         self.directory         = directory
    131         self.apiname           = apiname
    132         self.profile           = profile
    133         self.versions          = self.emptyRegex(versions)
    134         self.emitversions      = self.emptyRegex(emitversions)
    135         self.defaultExtensions = defaultExtensions
    136         self.addExtensions     = self.emptyRegex(addExtensions)
    137         self.removeExtensions  = self.emptyRegex(removeExtensions)
    138         self.sortProcedure     = sortProcedure
    139     #
    140     # Substitute a regular expression which matches no version
    141     # or extension names for None or the empty string.
    142     def emptyRegex(self,pat):
    143         if (pat == None or pat == ''):
    144             return '_nomatch_^'
    145         else:
    146             return pat
    147 
    148 # OutputGenerator - base class for generating API interfaces.
    149 # Manages basic logic, logging, and output file control
    150 # Derived classes actually generate formatted output.
    151 #
    152 # ---- methods ----
    153 # OutputGenerator(errFile, warnFile, diagFile)
    154 #   errFile, warnFile, diagFile - file handles to write errors,
    155 #     warnings, diagnostics to. May be None to not write.
    156 # logMsg(level, *args) - log messages of different categories
    157 #   level - 'error', 'warn', or 'diag'. 'error' will also
    158 #     raise a UserWarning exception
    159 #   *args - print()-style arguments
    160 # setExtMap(map) - specify a dictionary map from extension names to
    161 #   numbers, used in creating values for extension enumerants.
    162 # makeDir(directory) - create a directory, if not already done.
    163 #   Generally called from derived generators creating hierarchies.
    164 # beginFile(genOpts) - start a new interface file
    165 #   genOpts - GeneratorOptions controlling what's generated and how
    166 # endFile() - finish an interface file, closing it when done
    167 # beginFeature(interface, emit) - write interface for a feature
    168 # and tag generated features as having been done.
    169 #   interface - element for the <version> / <extension> to generate
    170 #   emit - actually write to the header only when True
    171 # endFeature() - finish an interface.
    172 # genType(typeinfo,name) - generate interface for a type
    173 #   typeinfo - TypeInfo for a type
    174 # genStruct(typeinfo,name) - generate interface for a C "struct" type.
    175 #   typeinfo - TypeInfo for a type interpreted as a struct
    176 # genGroup(groupinfo,name) - generate interface for a group of enums (C "enum")
    177 #   groupinfo - GroupInfo for a group
    178 # genEnum(enuminfo, name) - generate interface for an enum (constant)
    179 #   enuminfo - EnumInfo for an enum
    180 #   name - enum name
    181 # genCmd(cmdinfo) - generate interface for a command
    182 #   cmdinfo - CmdInfo for a command
    183 # isEnumRequired(enumElem) - return True if this <enum> element is required
    184 #   elem - <enum> element to test
    185 # makeCDecls(cmd) - return C prototype and function pointer typedef for a
    186 #     <command> Element, as a list of two strings
    187 #   cmd - Element for the <command>
    188 # newline() - print a newline to the output file (utility function)
    189 #
    190 class OutputGenerator:
    191     """Generate specified API interfaces in a specific style, such as a C header"""
    192     #
    193     # categoryToPath - map XML 'category' to include file directory name
    194     categoryToPath = {
    195         'bitmask'      : 'flags',
    196         'enum'         : 'enums',
    197         'funcpointer'  : 'funcpointers',
    198         'handle'       : 'handles',
    199         'define'       : 'defines',
    200         'basetype'     : 'basetypes',
    201     }
    202     #
    203     # Constructor
    204     def __init__(self,
    205                  errFile = sys.stderr,
    206                  warnFile = sys.stderr,
    207                  diagFile = sys.stdout):
    208         self.outFile = None
    209         self.errFile = errFile
    210         self.warnFile = warnFile
    211         self.diagFile = diagFile
    212         # Internal state
    213         self.featureName = None
    214         self.genOpts = None
    215         self.registry = None
    216         # Used for extension enum value generation
    217         self.extBase      = 1000000000
    218         self.extBlockSize = 1000
    219         self.madeDirs = {}
    220     #
    221     # logMsg - write a message of different categories to different
    222     #   destinations.
    223     # level -
    224     #   'diag' (diagnostic, voluminous)
    225     #   'warn' (warning)
    226     #   'error' (fatal error - raises exception after logging)
    227     # *args - print()-style arguments to direct to corresponding log
    228     def logMsg(self, level, *args):
    229         """Log a message at the given level. Can be ignored or log to a file"""
    230         if (level == 'error'):
    231             strfile = io.StringIO()
    232             write('ERROR:', *args, file=strfile)
    233             if (self.errFile != None):
    234                 write(strfile.getvalue(), file=self.errFile)
    235             raise UserWarning(strfile.getvalue())
    236         elif (level == 'warn'):
    237             if (self.warnFile != None):
    238                 write('WARNING:', *args, file=self.warnFile)
    239         elif (level == 'diag'):
    240             if (self.diagFile != None):
    241                 write('DIAG:', *args, file=self.diagFile)
    242         else:
    243             raise UserWarning(
    244                 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
    245     #
    246     # enumToValue - parses and converts an <enum> tag into a value.
    247     # Returns a list
    248     #   first element - integer representation of the value, or None
    249     #       if needsNum is False. The value must be a legal number
    250     #       if needsNum is True.
    251     #   second element - string representation of the value
    252     # There are several possible representations of values.
    253     #   A 'value' attribute simply contains the value.
    254     #   A 'bitpos' attribute defines a value by specifying the bit
    255     #       position which is set in that value.
    256     #   A 'offset','extbase','extends' triplet specifies a value
    257     #       as an offset to a base value defined by the specified
    258     #       'extbase' extension name, which is then cast to the
    259     #       typename specified by 'extends'. This requires probing
    260     #       the registry database, and imbeds knowledge of the
    261     #       Vulkan extension enum scheme in this function.
    262     def enumToValue(self, elem, needsNum):
    263         name = elem.get('name')
    264         numVal = None
    265         if ('value' in elem.keys()):
    266             value = elem.get('value')
    267             # print('About to translate value =', value, 'type =', type(value))
    268             if (needsNum):
    269                 numVal = int(value, 0)
    270             # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or
    271             # 'ull'), append it to the string value.
    272             # t = enuminfo.elem.get('type')
    273             # if (t != None and t != '' and t != 'i' and t != 's'):
    274             #     value += enuminfo.type
    275             self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
    276             return [numVal, value]
    277         if ('bitpos' in elem.keys()):
    278             value = elem.get('bitpos')
    279             numVal = int(value, 0)
    280             numVal = 1 << numVal
    281             value = '0x%08x' % numVal
    282             self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
    283             return [numVal, value]
    284         if ('offset' in elem.keys()):
    285             # Obtain values in the mapping from the attributes
    286             enumNegative = False
    287             offset = int(elem.get('offset'),0)
    288             extnumber = int(elem.get('extnumber'),0)
    289             extends = elem.get('extends')
    290             if ('dir' in elem.keys()):
    291                 enumNegative = True
    292             self.logMsg('diag', 'Enum', name, 'offset =', offset,
    293                 'extnumber =', extnumber, 'extends =', extends,
    294                 'enumNegative =', enumNegative)
    295             # Now determine the actual enumerant value, as defined
    296             # in the "Layers and Extensions" appendix of the spec.
    297             numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
    298             if (enumNegative):
    299                 numVal = -numVal
    300             value = '%d' % numVal
    301             # More logic needed!
    302             self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
    303             return [numVal, value]
    304         return [None, None]
    305     #
    306     def makeDir(self, path):
    307         self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
    308         if not (path in self.madeDirs.keys()):
    309             # This can get race conditions with multiple writers, see
    310             # https://stackoverflow.com/questions/273192/
    311             if not os.path.exists(path):
    312                 os.makedirs(path)
    313             self.madeDirs[path] = None
    314     #
    315     def beginFile(self, genOpts):
    316         self.genOpts = genOpts
    317         #
    318         # Open specified output file. Not done in constructor since a
    319         # Generator can be used without writing to a file.
    320         if (self.genOpts.filename != None):
    321             self.outFile = open(self.genOpts.directory + '/' + self.genOpts.filename, 'w')
    322         else:
    323             self.outFile = sys.stdout
    324     def endFile(self):
    325         self.errFile and self.errFile.flush()
    326         self.warnFile and self.warnFile.flush()
    327         self.diagFile and self.diagFile.flush()
    328         self.outFile.flush()
    329         if (self.outFile != sys.stdout and self.outFile != sys.stderr):
    330             self.outFile.close()
    331         self.genOpts = None
    332     #
    333     def beginFeature(self, interface, emit):
    334         self.emit = emit
    335         self.featureName = interface.get('name')
    336         # If there's an additional 'protect' attribute in the feature, save it
    337         self.featureExtraProtect = interface.get('protect')
    338     def endFeature(self):
    339         # Derived classes responsible for emitting feature
    340         self.featureName = None
    341         self.featureExtraProtect = None
    342     # Utility method to validate we're generating something only inside a
    343     # <feature> tag
    344     def validateFeature(self, featureType, featureName):
    345         if (self.featureName == None):
    346             raise UserWarning('Attempt to generate', featureType, name,
    347                     'when not in feature')
    348     #
    349     # Type generation
    350     def genType(self, typeinfo, name):
    351         self.validateFeature('type', name)
    352     #
    353     # Struct (e.g. C "struct" type) generation
    354     def genStruct(self, typeinfo, name):
    355         self.validateFeature('struct', name)
    356     #
    357     # Group (e.g. C "enum" type) generation
    358     def genGroup(self, groupinfo, name):
    359         self.validateFeature('group', name)
    360     #
    361     # Enumerant (really, constant) generation
    362     def genEnum(self, enuminfo, name):
    363         self.validateFeature('enum', name)
    364     #
    365     # Command generation
    366     def genCmd(self, cmd, name):
    367         self.validateFeature('command', name)
    368     #
    369     # Utility functions - turn a <proto> <name> into C-language prototype
    370     # and typedef declarations for that name.
    371     # name - contents of <name> tag
    372     # tail - whatever text follows that tag in the Element
    373     def makeProtoName(self, name, tail):
    374         return self.genOpts.apientry + name + tail
    375     def makeTypedefName(self, name, tail):
    376        return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
    377     #
    378     # makeCParamDecl - return a string which is an indented, formatted
    379     # declaration for a <param> or <member> block (e.g. function parameter
    380     # or structure/union member).
    381     # param - Element (<param> or <member>) to format
    382     # aligncol - if non-zero, attempt to align the nested <name> element
    383     #   at this column
    384     def makeCParamDecl(self, param, aligncol):
    385         paramdecl = '    ' + noneStr(param.text)
    386         for elem in param:
    387             text = noneStr(elem.text)
    388             tail = noneStr(elem.tail)
    389             if (elem.tag == 'name' and aligncol > 0):
    390                 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
    391                 # Align at specified column, if possible
    392                 paramdecl = paramdecl.rstrip()
    393                 oldLen = len(paramdecl)
    394                 # This works around a problem where very long type names -
    395                 # longer than the alignment column - would run into the tail
    396                 # text.
    397                 paramdecl = paramdecl.ljust(aligncol-1) + ' '
    398                 newLen = len(paramdecl)
    399                 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
    400             paramdecl += text + tail
    401         return paramdecl
    402     #
    403     # getCParamTypeLength - return the length of the type field is an indented, formatted
    404     # declaration for a <param> or <member> block (e.g. function parameter
    405     # or structure/union member).
    406     # param - Element (<param> or <member>) to identify
    407     def getCParamTypeLength(self, param):
    408         paramdecl = '    ' + noneStr(param.text)
    409         for elem in param:
    410             text = noneStr(elem.text)
    411             tail = noneStr(elem.tail)
    412             if (elem.tag == 'name'):
    413                 # Align at specified column, if possible
    414                 newLen = len(paramdecl.rstrip())
    415                 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
    416             paramdecl += text + tail
    417         return newLen
    418     #
    419     # isEnumRequired(elem) - return True if this <enum> element is
    420     # required, False otherwise
    421     # elem - <enum> element to test
    422     def isEnumRequired(self, elem):
    423         return (elem.get('extname') is None or
    424                 re.match(self.genOpts.addExtensions, elem.get('extname')) is not None or
    425                 self.genOpts.defaultExtensions == elem.get('supported'))
    426     #
    427     # makeCDecls - return C prototype and function pointer typedef for a
    428     #   command, as a two-element list of strings.
    429     # cmd - Element containing a <command> tag
    430     def makeCDecls(self, cmd):
    431         """Generate C function pointer typedef for <command> Element"""
    432         proto = cmd.find('proto')
    433         params = cmd.findall('param')
    434         # Begin accumulating prototype and typedef strings
    435         pdecl = self.genOpts.apicall
    436         tdecl = 'typedef '
    437         #
    438         # Insert the function return type/name.
    439         # For prototypes, add APIENTRY macro before the name
    440         # For typedefs, add (APIENTRY *<name>) around the name and
    441         #   use the PFN_cmdnameproc naming convention.
    442         # Done by walking the tree for <proto> element by element.
    443         # etree has elem.text followed by (elem[i], elem[i].tail)
    444         #   for each child element and any following text
    445         # Leading text
    446         pdecl += noneStr(proto.text)
    447         tdecl += noneStr(proto.text)
    448         # For each child element, if it's a <name> wrap in appropriate
    449         # declaration. Otherwise append its contents and tail contents.
    450         for elem in proto:
    451             text = noneStr(elem.text)
    452             tail = noneStr(elem.tail)
    453             if (elem.tag == 'name'):
    454                 pdecl += self.makeProtoName(text, tail)
    455                 tdecl += self.makeTypedefName(text, tail)
    456             else:
    457                 pdecl += text + tail
    458                 tdecl += text + tail
    459         # Now add the parameter declaration list, which is identical
    460         # for prototypes and typedefs. Concatenate all the text from
    461         # a <param> node without the tags. No tree walking required
    462         # since all tags are ignored.
    463         # Uses: self.indentFuncProto
    464         # self.indentFuncPointer
    465         # self.alignFuncParam
    466         # Might be able to doubly-nest the joins, e.g.
    467         #   ','.join(('_'.join([l[i] for i in range(0,len(l))])
    468         n = len(params)
    469         # Indented parameters
    470         if n > 0:
    471             indentdecl = '(\n'
    472             for i in range(0,n):
    473                 paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam)
    474                 if (i < n - 1):
    475                     paramdecl += ',\n'
    476                 else:
    477                     paramdecl += ');'
    478                 indentdecl += paramdecl
    479         else:
    480             indentdecl = '(void);'
    481         # Non-indented parameters
    482         paramdecl = '('
    483         if n > 0:
    484             for i in range(0,n):
    485                 paramdecl += ''.join([t for t in params[i].itertext()])
    486                 if (i < n - 1):
    487                     paramdecl += ', '
    488         else:
    489             paramdecl += 'void'
    490         paramdecl += ");";
    491         return [ pdecl + indentdecl, tdecl + paramdecl ]
    492     #
    493     def newline(self):
    494         write('', file=self.outFile)
    495 
    496     def setRegistry(self, registry):
    497         self.registry = registry
    498         #
    499