1#!/usr/bin/python3 -i 2import os,re,sys 3from collections import namedtuple 4from lxml import etree 5 6def write( *args, **kwargs ): 7 file = kwargs.pop('file',sys.stdout) 8 end = kwargs.pop( 'end','\n') 9 file.write( ' '.join([str(arg) for arg in args]) ) 10 file.write( end ) 11 12# noneStr - returns string argument, or "" if argument is None. 13# Used in converting lxml Elements into text. 14# str - string to convert 15def noneStr(str): 16 if (str): 17 return str 18 else: 19 return "" 20 21# enquote - returns string argument with surrounding quotes, 22# for serialization into Python code. 23def enquote(str): 24 if (str): 25 return "'" + str + "'" 26 else: 27 return None 28 29# Primary sort key for regSortFeatures. 30# Sorts by category of the feature name string: 31# Core API features (those defined with a <feature> tag) 32# ARB/KHR/OES (Khronos extensions) 33# other (EXT/vendor extensions) 34# This will need changing for Vulkan! 35def regSortCategoryKey(feature): 36 if (feature.elem.tag == 'feature'): 37 return 0 38 elif (feature.category == 'ARB' or 39 feature.category == 'KHR' or 40 feature.category == 'OES'): 41 return 1 42 else: 43 return 2 44 45# Secondary sort key for regSortFeatures. 46# Sorts by extension name. 47def regSortNameKey(feature): 48 return feature.name 49 50# Second sort key for regSortFeatures. 51# Sorts by feature version. <extension> elements all have version number "0" 52def regSortFeatureVersionKey(feature): 53 return float(feature.version) 54 55# Tertiary sort key for regSortFeatures. 56# Sorts by extension number. <feature> elements all have extension number 0. 57def regSortExtensionNumberKey(feature): 58 return int(feature.number) 59 60# regSortFeatures - default sort procedure for features. 61# Sorts by primary key of feature category ('feature' or 'extension') 62# then by version number (for features) 63# then by extension number (for extensions) 64def regSortFeatures(featureList): 65 featureList.sort(key = regSortExtensionNumberKey) 66 featureList.sort(key = regSortFeatureVersionKey) 67 featureList.sort(key = regSortCategoryKey) 68 69# GeneratorOptions - base class for options used during header production 70# These options are target language independent, and used by 71# Registry.apiGen() and by base OutputGenerator objects. 72# 73# Members 74# filename - name of file to generate, or None to write to stdout. 75# apiname - string matching <api> 'apiname' attribute, e.g. 'gl'. 76# profile - string specifying API profile , e.g. 'core', or None. 77# versions - regex matching API versions to process interfaces for. 78# Normally '.*' or '[0-9]\.[0-9]' to match all defined versions. 79# emitversions - regex matching API versions to actually emit 80# interfaces for (though all requested versions are considered 81# when deciding which interfaces to generate). For GL 4.3 glext.h, 82# this might be '1\.[2-5]|[2-4]\.[0-9]'. 83# defaultExtensions - If not None, a string which must in its 84# entirety match the pattern in the "supported" attribute of 85# the <extension>. Defaults to None. Usually the same as apiname. 86# addExtensions - regex matching names of additional extensions 87# to include. Defaults to None. 88# removeExtensions - regex matching names of extensions to 89# remove (after defaultExtensions and addExtensions). Defaults 90# to None. 91# sortProcedure - takes a list of FeatureInfo objects and sorts 92# them in place to a preferred order in the generated output. 93# Default is core API versions, ARB/KHR/OES extensions, all 94# other extensions, alphabetically within each group. 95# The regex patterns can be None or empty, in which case they match 96# nothing. 97class GeneratorOptions: 98 """Represents options during header production from an API registry""" 99 def __init__(self, 100 filename = None, 101 apiname = None, 102 profile = None, 103 versions = '.*', 104 emitversions = '.*', 105 defaultExtensions = None, 106 addExtensions = None, 107 removeExtensions = None, 108 sortProcedure = regSortFeatures): 109 self.filename = filename 110 self.apiname = apiname 111 self.profile = profile 112 self.versions = self.emptyRegex(versions) 113 self.emitversions = self.emptyRegex(emitversions) 114 self.defaultExtensions = defaultExtensions 115 self.addExtensions = self.emptyRegex(addExtensions) 116 self.removeExtensions = self.emptyRegex(removeExtensions) 117 self.sortProcedure = sortProcedure 118 # 119 # Substitute a regular expression which matches no version 120 # or extension names for None or the empty string. 121 def emptyRegex(self,pat): 122 if (pat == None or pat == ''): 123 return '_nomatch_^' 124 else: 125 return pat 126 127# CGeneratorOptions - subclass of GeneratorOptions. 128# 129# Adds options used by COutputGenerator objects during C language header 130# generation. 131# 132# Additional members 133# prefixText - list of strings to prefix generated header with 134# (usually a copyright statement + calling convention macros). 135# protectFile - True if multiple inclusion protection should be 136# generated (based on the filename) around the entire header. 137# protectFeature - True if #ifndef..#endif protection should be 138# generated around a feature interface in the header file. 139# genFuncPointers - True if function pointer typedefs should be 140# generated 141# protectProto - If conditional protection should be generated 142# around prototype declarations, set to either '#ifdef' 143# to require opt-in (#ifdef protectProtoStr) or '#ifndef' 144# to require opt-out (#ifndef protectProtoStr). Otherwise 145# set to None. 146# protectProtoStr - #ifdef/#ifndef symbol to use around prototype 147# declarations, if protectProto is set 148# apicall - string to use for the function declaration prefix, 149# such as APICALL on Windows. 150# apientry - string to use for the calling convention macro, 151# in typedefs, such as APIENTRY. 152# apientryp - string to use for the calling convention macro 153# in function pointer typedefs, such as APIENTRYP. 154# indentFuncProto - True if prototype declarations should put each 155# parameter on a separate line 156# indentFuncPointer - True if typedefed function pointers should put each 157# parameter on a separate line 158# alignFuncParam - if nonzero and parameters are being put on a 159# separate line, align parameter names at the specified column 160class CGeneratorOptions(GeneratorOptions): 161 """Represents options during C interface generation for headers""" 162 def __init__(self, 163 filename = None, 164 apiname = None, 165 profile = None, 166 versions = '.*', 167 emitversions = '.*', 168 defaultExtensions = None, 169 addExtensions = None, 170 removeExtensions = None, 171 sortProcedure = regSortFeatures, 172 prefixText = "", 173 genFuncPointers = True, 174 protectFile = True, 175 protectFeature = True, 176 protectProto = None, 177 protectProtoStr = None, 178 apicall = '', 179 apientry = '', 180 apientryp = '', 181 indentFuncProto = True, 182 indentFuncPointer = False, 183 alignFuncParam = 0): 184 GeneratorOptions.__init__(self, filename, apiname, profile, 185 versions, emitversions, defaultExtensions, 186 addExtensions, removeExtensions, sortProcedure) 187 self.prefixText = prefixText 188 self.genFuncPointers = genFuncPointers 189 self.protectFile = protectFile 190 self.protectFeature = protectFeature 191 self.protectProto = protectProto 192 self.protectProtoStr = protectProtoStr 193 self.apicall = apicall 194 self.apientry = apientry 195 self.apientryp = apientryp 196 self.indentFuncProto = indentFuncProto 197 self.indentFuncPointer = indentFuncPointer 198 self.alignFuncParam = alignFuncParam 199 200# DocGeneratorOptions - subclass of GeneratorOptions. 201# 202# Shares many members with CGeneratorOptions, since 203# both are writing C-style declarations: 204# 205# prefixText - list of strings to prefix generated header with 206# (usually a copyright statement + calling convention macros). 207# apicall - string to use for the function declaration prefix, 208# such as APICALL on Windows. 209# apientry - string to use for the calling convention macro, 210# in typedefs, such as APIENTRY. 211# apientryp - string to use for the calling convention macro 212# in function pointer typedefs, such as APIENTRYP. 213# genDirectory - directory into which to generate include files 214# indentFuncProto - True if prototype declarations should put each 215# parameter on a separate line 216# indentFuncPointer - True if typedefed function pointers should put each 217# parameter on a separate line 218# alignFuncParam - if nonzero and parameters are being put on a 219# separate line, align parameter names at the specified column 220# 221# Additional members: 222# 223class DocGeneratorOptions(GeneratorOptions): 224 """Represents options during C interface generation for Asciidoc""" 225 def __init__(self, 226 filename = None, 227 apiname = None, 228 profile = None, 229 versions = '.*', 230 emitversions = '.*', 231 defaultExtensions = None, 232 addExtensions = None, 233 removeExtensions = None, 234 sortProcedure = regSortFeatures, 235 prefixText = "", 236 apicall = '', 237 apientry = '', 238 apientryp = '', 239 genDirectory = 'gen', 240 indentFuncProto = True, 241 indentFuncPointer = False, 242 alignFuncParam = 0, 243 expandEnumerants = True): 244 GeneratorOptions.__init__(self, filename, apiname, profile, 245 versions, emitversions, defaultExtensions, 246 addExtensions, removeExtensions, sortProcedure) 247 self.prefixText = prefixText 248 self.apicall = apicall 249 self.apientry = apientry 250 self.apientryp = apientryp 251 self.genDirectory = genDirectory 252 self.indentFuncProto = indentFuncProto 253 self.indentFuncPointer = indentFuncPointer 254 self.alignFuncParam = alignFuncParam 255 self.expandEnumerants = expandEnumerants 256 257# ThreadGeneratorOptions - subclass of GeneratorOptions. 258# 259# Adds options used by COutputGenerator objects during C language header 260# generation. 261# 262# Additional members 263# prefixText - list of strings to prefix generated header with 264# (usually a copyright statement + calling convention macros). 265# protectFile - True if multiple inclusion protection should be 266# generated (based on the filename) around the entire header. 267# protectFeature - True if #ifndef..#endif protection should be 268# generated around a feature interface in the header file. 269# genFuncPointers - True if function pointer typedefs should be 270# generated 271# protectProto - True if #ifdef..#endif protection should be 272# generated around prototype declarations 273# protectProtoStr - #ifdef symbol to use around prototype 274# declarations, if protected 275# apicall - string to use for the function declaration prefix, 276# such as APICALL on Windows. 277# apientry - string to use for the calling convention macro, 278# in typedefs, such as APIENTRY. 279# apientryp - string to use for the calling convention macro 280# in function pointer typedefs, such as APIENTRYP. 281# indentFuncProto - True if prototype declarations should put each 282# parameter on a separate line 283# indentFuncPointer - True if typedefed function pointers should put each 284# parameter on a separate line 285# alignFuncParam - if nonzero and parameters are being put on a 286# separate line, align parameter names at the specified column 287class ThreadGeneratorOptions(GeneratorOptions): 288 """Represents options during C interface generation for headers""" 289 def __init__(self, 290 filename = None, 291 apiname = None, 292 profile = None, 293 versions = '.*', 294 emitversions = '.*', 295 defaultExtensions = None, 296 addExtensions = None, 297 removeExtensions = None, 298 sortProcedure = regSortFeatures, 299 prefixText = "", 300 genFuncPointers = True, 301 protectFile = True, 302 protectFeature = True, 303 protectProto = True, 304 protectProtoStr = True, 305 apicall = '', 306 apientry = '', 307 apientryp = '', 308 indentFuncProto = True, 309 indentFuncPointer = False, 310 alignFuncParam = 0): 311 GeneratorOptions.__init__(self, filename, apiname, profile, 312 versions, emitversions, defaultExtensions, 313 addExtensions, removeExtensions, sortProcedure) 314 self.prefixText = prefixText 315 self.genFuncPointers = genFuncPointers 316 self.protectFile = protectFile 317 self.protectFeature = protectFeature 318 self.protectProto = protectProto 319 self.protectProtoStr = protectProtoStr 320 self.apicall = apicall 321 self.apientry = apientry 322 self.apientryp = apientryp 323 self.indentFuncProto = indentFuncProto 324 self.indentFuncPointer = indentFuncPointer 325 self.alignFuncParam = alignFuncParam 326 327 328# ParamCheckerGeneratorOptions - subclass of GeneratorOptions. 329# 330# Adds options used by ParamCheckerOutputGenerator objects during parameter validation 331# generation. 332# 333# Additional members 334# prefixText - list of strings to prefix generated header with 335# (usually a copyright statement + calling convention macros). 336# protectFile - True if multiple inclusion protection should be 337# generated (based on the filename) around the entire header. 338# protectFeature - True if #ifndef..#endif protection should be 339# generated around a feature interface in the header file. 340# genFuncPointers - True if function pointer typedefs should be 341# generated 342# protectProto - If conditional protection should be generated 343# around prototype declarations, set to either '#ifdef' 344# to require opt-in (#ifdef protectProtoStr) or '#ifndef' 345# to require opt-out (#ifndef protectProtoStr). Otherwise 346# set to None. 347# protectProtoStr - #ifdef/#ifndef symbol to use around prototype 348# declarations, if protectProto is set 349# apicall - string to use for the function declaration prefix, 350# such as APICALL on Windows. 351# apientry - string to use for the calling convention macro, 352# in typedefs, such as APIENTRY. 353# apientryp - string to use for the calling convention macro 354# in function pointer typedefs, such as APIENTRYP. 355# indentFuncProto - True if prototype declarations should put each 356# parameter on a separate line 357# indentFuncPointer - True if typedefed function pointers should put each 358# parameter on a separate line 359# alignFuncParam - if nonzero and parameters are being put on a 360# separate line, align parameter names at the specified column 361class ParamCheckerGeneratorOptions(GeneratorOptions): 362 """Represents options during C interface generation for headers""" 363 def __init__(self, 364 filename = None, 365 apiname = None, 366 profile = None, 367 versions = '.*', 368 emitversions = '.*', 369 defaultExtensions = None, 370 addExtensions = None, 371 removeExtensions = None, 372 sortProcedure = regSortFeatures, 373 prefixText = "", 374 genFuncPointers = True, 375 protectFile = True, 376 protectFeature = True, 377 protectProto = None, 378 protectProtoStr = None, 379 apicall = '', 380 apientry = '', 381 apientryp = '', 382 indentFuncProto = True, 383 indentFuncPointer = False, 384 alignFuncParam = 0): 385 GeneratorOptions.__init__(self, filename, apiname, profile, 386 versions, emitversions, defaultExtensions, 387 addExtensions, removeExtensions, sortProcedure) 388 self.prefixText = prefixText 389 self.genFuncPointers = genFuncPointers 390 self.protectFile = protectFile 391 self.protectFeature = protectFeature 392 self.protectProto = protectProto 393 self.protectProtoStr = protectProtoStr 394 self.apicall = apicall 395 self.apientry = apientry 396 self.apientryp = apientryp 397 self.indentFuncProto = indentFuncProto 398 self.indentFuncPointer = indentFuncPointer 399 self.alignFuncParam = alignFuncParam 400 401 402# OutputGenerator - base class for generating API interfaces. 403# Manages basic logic, logging, and output file control 404# Derived classes actually generate formatted output. 405# 406# ---- methods ---- 407# OutputGenerator(errFile, warnFile, diagFile) 408# errFile, warnFile, diagFile - file handles to write errors, 409# warnings, diagnostics to. May be None to not write. 410# logMsg(level, *args) - log messages of different categories 411# level - 'error', 'warn', or 'diag'. 'error' will also 412# raise a UserWarning exception 413# *args - print()-style arguments 414# setExtMap(map) - specify a dictionary map from extension names to 415# numbers, used in creating values for extension enumerants. 416# beginFile(genOpts) - start a new interface file 417# genOpts - GeneratorOptions controlling what's generated and how 418# endFile() - finish an interface file, closing it when done 419# beginFeature(interface, emit) - write interface for a feature 420# and tag generated features as having been done. 421# interface - element for the <version> / <extension> to generate 422# emit - actually write to the header only when True 423# endFeature() - finish an interface. 424# genType(typeinfo,name) - generate interface for a type 425# typeinfo - TypeInfo for a type 426# genStruct(typeinfo,name) - generate interface for a C "struct" type. 427# typeinfo - TypeInfo for a type interpreted as a struct 428# genGroup(groupinfo,name) - generate interface for a group of enums (C "enum") 429# groupinfo - GroupInfo for a group 430# genEnum(enuminfo, name) - generate interface for an enum (constant) 431# enuminfo - EnumInfo for an enum 432# name - enum name 433# genCmd(cmdinfo) - generate interface for a command 434# cmdinfo - CmdInfo for a command 435# makeCDecls(cmd) - return C prototype and function pointer typedef for a 436# <command> Element, as a list of two strings 437# cmd - Element for the <command> 438# newline() - print a newline to the output file (utility function) 439# 440class OutputGenerator: 441 """Generate specified API interfaces in a specific style, such as a C header""" 442 def __init__(self, 443 errFile = sys.stderr, 444 warnFile = sys.stderr, 445 diagFile = sys.stdout): 446 self.outFile = None 447 self.errFile = errFile 448 self.warnFile = warnFile 449 self.diagFile = diagFile 450 # Internal state 451 self.featureName = None 452 self.genOpts = None 453 self.registry = None 454 # Used for extension enum value generation 455 self.extBase = 1000000000 456 self.extBlockSize = 1000 457 # 458 # logMsg - write a message of different categories to different 459 # destinations. 460 # level - 461 # 'diag' (diagnostic, voluminous) 462 # 'warn' (warning) 463 # 'error' (fatal error - raises exception after logging) 464 # *args - print()-style arguments to direct to corresponding log 465 def logMsg(self, level, *args): 466 """Log a message at the given level. Can be ignored or log to a file""" 467 if (level == 'error'): 468 strfile = io.StringIO() 469 write('ERROR:', *args, file=strfile) 470 if (self.errFile != None): 471 write(strfile.getvalue(), file=self.errFile) 472 raise UserWarning(strfile.getvalue()) 473 elif (level == 'warn'): 474 if (self.warnFile != None): 475 write('WARNING:', *args, file=self.warnFile) 476 elif (level == 'diag'): 477 if (self.diagFile != None): 478 write('DIAG:', *args, file=self.diagFile) 479 else: 480 raise UserWarning( 481 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) 482 # 483 # enumToValue - parses and converts an <enum> tag into a value. 484 # Returns a list 485 # first element - integer representation of the value, or None 486 # if needsNum is False. The value must be a legal number 487 # if needsNum is True. 488 # second element - string representation of the value 489 # There are several possible representations of values. 490 # A 'value' attribute simply contains the value. 491 # A 'bitpos' attribute defines a value by specifying the bit 492 # position which is set in that value. 493 # A 'offset','extbase','extends' triplet specifies a value 494 # as an offset to a base value defined by the specified 495 # 'extbase' extension name, which is then cast to the 496 # typename specified by 'extends'. This requires probing 497 # the registry database, and imbeds knowledge of the 498 # Vulkan extension enum scheme in this function. 499 def enumToValue(self, elem, needsNum): 500 name = elem.get('name') 501 numVal = None 502 if ('value' in elem.keys()): 503 value = elem.get('value') 504 # print('About to translate value =', value, 'type =', type(value)) 505 if (needsNum): 506 numVal = int(value, 0) 507 # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or 508 # 'ull'), append it to the string value. 509 # t = enuminfo.elem.get('type') 510 # if (t != None and t != '' and t != 'i' and t != 's'): 511 # value += enuminfo.type 512 self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']') 513 return [numVal, value] 514 if ('bitpos' in elem.keys()): 515 value = elem.get('bitpos') 516 numVal = int(value, 0) 517 numVal = 1 << numVal 518 value = '0x%08x' % numVal 519 self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') 520 return [numVal, value] 521 if ('offset' in elem.keys()): 522 # Obtain values in the mapping from the attributes 523 enumNegative = False 524 offset = int(elem.get('offset'),0) 525 extnumber = int(elem.get('extnumber'),0) 526 extends = elem.get('extends') 527 if ('dir' in elem.keys()): 528 enumNegative = True 529 self.logMsg('diag', 'Enum', name, 'offset =', offset, 530 'extnumber =', extnumber, 'extends =', extends, 531 'enumNegative =', enumNegative) 532 # Now determine the actual enumerant value, as defined 533 # in the "Layers and Extensions" appendix of the spec. 534 numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset 535 if (enumNegative): 536 numVal = -numVal 537 value = '%d' % numVal 538 # More logic needed! 539 self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') 540 return [numVal, value] 541 return [None, None] 542 # 543 def beginFile(self, genOpts): 544 self.genOpts = genOpts 545 # 546 # Open specified output file. Not done in constructor since a 547 # Generator can be used without writing to a file. 548 if (self.genOpts.filename != None): 549 self.outFile = open(self.genOpts.filename, 'w') 550 else: 551 self.outFile = sys.stdout 552 def endFile(self): 553 self.errFile and self.errFile.flush() 554 self.warnFile and self.warnFile.flush() 555 self.diagFile and self.diagFile.flush() 556 self.outFile.flush() 557 if (self.outFile != sys.stdout and self.outFile != sys.stderr): 558 self.outFile.close() 559 self.genOpts = None 560 # 561 def beginFeature(self, interface, emit): 562 self.emit = emit 563 self.featureName = interface.get('name') 564 # If there's an additional 'protect' attribute in the feature, save it 565 self.featureExtraProtect = interface.get('protect') 566 def endFeature(self): 567 # Derived classes responsible for emitting feature 568 self.featureName = None 569 self.featureExtraProtect = None 570 # Utility method to validate we're generating something only inside a 571 # <feature> tag 572 def validateFeature(self, featureType, featureName): 573 if (self.featureName == None): 574 raise UserWarning('Attempt to generate', featureType, name, 575 'when not in feature') 576 # 577 # Type generation 578 def genType(self, typeinfo, name): 579 self.validateFeature('type', name) 580 # 581 # Struct (e.g. C "struct" type) generation 582 def genStruct(self, typeinfo, name): 583 self.validateFeature('struct', name) 584 # 585 # Group (e.g. C "enum" type) generation 586 def genGroup(self, groupinfo, name): 587 self.validateFeature('group', name) 588 # 589 # Enumerant (really, constant) generation 590 def genEnum(self, enuminfo, name): 591 self.validateFeature('enum', name) 592 # 593 # Command generation 594 def genCmd(self, cmd, name): 595 self.validateFeature('command', name) 596 # 597 # Utility functions - turn a <proto> <name> into C-language prototype 598 # and typedef declarations for that name. 599 # name - contents of <name> tag 600 # tail - whatever text follows that tag in the Element 601 def makeProtoName(self, name, tail): 602 return self.genOpts.apientry + name + tail 603 def makeTypedefName(self, name, tail): 604 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' 605 # 606 # makeCParamDecl - return a string which is an indented, formatted 607 # declaration for a <param> or <member> block (e.g. function parameter 608 # or structure/union member). 609 # param - Element (<param> or <member>) to format 610 # aligncol - if non-zero, attempt to align the nested <name> element 611 # at this column 612 def makeCParamDecl(self, param, aligncol): 613 paramdecl = ' ' + noneStr(param.text) 614 for elem in param: 615 text = noneStr(elem.text) 616 tail = noneStr(elem.tail) 617 if (elem.tag == 'name' and aligncol > 0): 618 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) 619 # Align at specified column, if possible 620 paramdecl = paramdecl.rstrip() 621 oldLen = len(paramdecl) 622 paramdecl = paramdecl.ljust(aligncol) 623 newLen = len(paramdecl) 624 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) 625 paramdecl += text + tail 626 return paramdecl 627 # 628 # getCParamTypeLength - return the length of the type field is an indented, formatted 629 # declaration for a <param> or <member> block (e.g. function parameter 630 # or structure/union member). 631 # param - Element (<param> or <member>) to identify 632 def getCParamTypeLength(self, param): 633 paramdecl = ' ' + noneStr(param.text) 634 for elem in param: 635 text = noneStr(elem.text) 636 tail = noneStr(elem.tail) 637 if (elem.tag == 'name'): 638 # Align at specified column, if possible 639 newLen = len(paramdecl.rstrip()) 640 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) 641 paramdecl += text + tail 642 return newLen 643 # 644 # makeCDecls - return C prototype and function pointer typedef for a 645 # command, as a two-element list of strings. 646 # cmd - Element containing a <command> tag 647 def makeCDecls(self, cmd): 648 """Generate C function pointer typedef for <command> Element""" 649 proto = cmd.find('proto') 650 params = cmd.findall('param') 651 # Begin accumulating prototype and typedef strings 652 pdecl = self.genOpts.apicall 653 tdecl = 'typedef ' 654 # 655 # Insert the function return type/name. 656 # For prototypes, add APIENTRY macro before the name 657 # For typedefs, add (APIENTRY *<name>) around the name and 658 # use the PFN_cmdnameproc naming convention. 659 # Done by walking the tree for <proto> element by element. 660 # lxml.etree has elem.text followed by (elem[i], elem[i].tail) 661 # for each child element and any following text 662 # Leading text 663 pdecl += noneStr(proto.text) 664 tdecl += noneStr(proto.text) 665 # For each child element, if it's a <name> wrap in appropriate 666 # declaration. Otherwise append its contents and tail contents. 667 for elem in proto: 668 text = noneStr(elem.text) 669 tail = noneStr(elem.tail) 670 if (elem.tag == 'name'): 671 pdecl += self.makeProtoName(text, tail) 672 tdecl += self.makeTypedefName(text, tail) 673 else: 674 pdecl += text + tail 675 tdecl += text + tail 676 # Now add the parameter declaration list, which is identical 677 # for prototypes and typedefs. Concatenate all the text from 678 # a <param> node without the tags. No tree walking required 679 # since all tags are ignored. 680 # Uses: self.indentFuncProto 681 # self.indentFuncPointer 682 # self.alignFuncParam 683 # Might be able to doubly-nest the joins, e.g. 684 # ','.join(('_'.join([l[i] for i in range(0,len(l))]) 685 n = len(params) 686 # Indented parameters 687 if n > 0: 688 indentdecl = '(\n' 689 for i in range(0,n): 690 paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam) 691 if (i < n - 1): 692 paramdecl += ',\n' 693 else: 694 paramdecl += ');' 695 indentdecl += paramdecl 696 else: 697 indentdecl = '(void);' 698 # Non-indented parameters 699 paramdecl = '(' 700 if n > 0: 701 for i in range(0,n): 702 paramdecl += ''.join([t for t in params[i].itertext()]) 703 if (i < n - 1): 704 paramdecl += ', ' 705 else: 706 paramdecl += 'void' 707 paramdecl += ");"; 708 return [ pdecl + indentdecl, tdecl + paramdecl ] 709 # 710 def newline(self): 711 write('', file=self.outFile) 712 713 def setRegistry(self, registry): 714 self.registry = registry 715 # 716 717# COutputGenerator - subclass of OutputGenerator. 718# Generates C-language API interfaces. 719# 720# ---- methods ---- 721# COutputGenerator(errFile, warnFile, diagFile) - args as for 722# OutputGenerator. Defines additional internal state. 723# ---- methods overriding base class ---- 724# beginFile(genOpts) 725# endFile() 726# beginFeature(interface, emit) 727# endFeature() 728# genType(typeinfo,name) 729# genStruct(typeinfo,name) 730# genGroup(groupinfo,name) 731# genEnum(enuminfo, name) 732# genCmd(cmdinfo) 733class COutputGenerator(OutputGenerator): 734 """Generate specified API interfaces in a specific style, such as a C header""" 735 # This is an ordered list of sections in the header file. 736 TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', 737 'group', 'bitmask', 'funcpointer', 'struct'] 738 ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command'] 739 def __init__(self, 740 errFile = sys.stderr, 741 warnFile = sys.stderr, 742 diagFile = sys.stdout): 743 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 744 # Internal state - accumulators for different inner block text 745 self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) 746 # 747 def beginFile(self, genOpts): 748 OutputGenerator.beginFile(self, genOpts) 749 # C-specific 750 # 751 # Multiple inclusion protection & C++ wrappers. 752 if (genOpts.protectFile and self.genOpts.filename): 753 headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename)) 754 write('#ifndef', headerSym, file=self.outFile) 755 write('#define', headerSym, '1', file=self.outFile) 756 self.newline() 757 write('#ifdef __cplusplus', file=self.outFile) 758 write('extern "C" {', file=self.outFile) 759 write('#endif', file=self.outFile) 760 self.newline() 761 # 762 # User-supplied prefix text, if any (list of strings) 763 if (genOpts.prefixText): 764 for s in genOpts.prefixText: 765 write(s, file=self.outFile) 766 # 767 # Some boilerplate describing what was generated - this 768 # will probably be removed later since the extensions 769 # pattern may be very long. 770 # write('/* Generated C header for:', file=self.outFile) 771 # write(' * API:', genOpts.apiname, file=self.outFile) 772 # if (genOpts.profile): 773 # write(' * Profile:', genOpts.profile, file=self.outFile) 774 # write(' * Versions considered:', genOpts.versions, file=self.outFile) 775 # write(' * Versions emitted:', genOpts.emitversions, file=self.outFile) 776 # write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile) 777 # write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile) 778 # write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile) 779 # write(' */', file=self.outFile) 780 def endFile(self): 781 # C-specific 782 # Finish C++ wrapper and multiple inclusion protection 783 self.newline() 784 write('#ifdef __cplusplus', file=self.outFile) 785 write('}', file=self.outFile) 786 write('#endif', file=self.outFile) 787 if (self.genOpts.protectFile and self.genOpts.filename): 788 self.newline() 789 write('#endif', file=self.outFile) 790 # Finish processing in superclass 791 OutputGenerator.endFile(self) 792 def beginFeature(self, interface, emit): 793 # Start processing in superclass 794 OutputGenerator.beginFeature(self, interface, emit) 795 # C-specific 796 # Accumulate includes, defines, types, enums, function pointer typedefs, 797 # end function prototypes separately for this feature. They're only 798 # printed in endFeature(). 799 self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) 800 def endFeature(self): 801 # C-specific 802 # Actually write the interface to the output file. 803 if (self.emit): 804 self.newline() 805 if (self.genOpts.protectFeature): 806 write('#ifndef', self.featureName, file=self.outFile) 807 # If type declarations are needed by other features based on 808 # this one, it may be necessary to suppress the ExtraProtect, 809 # or move it below the 'for section...' loop. 810 if (self.featureExtraProtect != None): 811 write('#ifdef', self.featureExtraProtect, file=self.outFile) 812 write('#define', self.featureName, '1', file=self.outFile) 813 for section in self.TYPE_SECTIONS: 814 contents = self.sections[section] 815 if contents: 816 write('\n'.join(contents), file=self.outFile) 817 self.newline() 818 if (self.genOpts.genFuncPointers and self.sections['commandPointer']): 819 write('\n'.join(self.sections['commandPointer']), file=self.outFile) 820 self.newline() 821 if (self.sections['command']): 822 if (self.genOpts.protectProto): 823 write(self.genOpts.protectProto, 824 self.genOpts.protectProtoStr, file=self.outFile) 825 write('\n'.join(self.sections['command']), end='', file=self.outFile) 826 if (self.genOpts.protectProto): 827 write('#endif', file=self.outFile) 828 else: 829 self.newline() 830 if (self.featureExtraProtect != None): 831 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) 832 if (self.genOpts.protectFeature): 833 write('#endif /*', self.featureName, '*/', file=self.outFile) 834 # Finish processing in superclass 835 OutputGenerator.endFeature(self) 836 # 837 # Append a definition to the specified section 838 def appendSection(self, section, text): 839 # self.sections[section].append('SECTION: ' + section + '\n') 840 self.sections[section].append(text) 841 # 842 # Type generation 843 def genType(self, typeinfo, name): 844 OutputGenerator.genType(self, typeinfo, name) 845 typeElem = typeinfo.elem 846 # If the type is a struct type, traverse the imbedded <member> tags 847 # generating a structure. Otherwise, emit the tag text. 848 category = typeElem.get('category') 849 if (category == 'struct' or category == 'union'): 850 self.genStruct(typeinfo, name) 851 else: 852 # Replace <apientry /> tags with an APIENTRY-style string 853 # (from self.genOpts). Copy other text through unchanged. 854 # If the resulting text is an empty string, don't emit it. 855 s = noneStr(typeElem.text) 856 for elem in typeElem: 857 if (elem.tag == 'apientry'): 858 s += self.genOpts.apientry + noneStr(elem.tail) 859 else: 860 s += noneStr(elem.text) + noneStr(elem.tail) 861 if s: 862 # Add extra newline after multi-line entries. 863 if '\n' in s: 864 s += '\n' 865 self.appendSection(category, s) 866 # 867 # Struct (e.g. C "struct" type) generation. 868 # This is a special case of the <type> tag where the contents are 869 # interpreted as a set of <member> tags instead of freeform C 870 # C type declarations. The <member> tags are just like <param> 871 # tags - they are a declaration of a struct or union member. 872 # Only simple member declarations are supported (no nested 873 # structs etc.) 874 def genStruct(self, typeinfo, typeName): 875 OutputGenerator.genStruct(self, typeinfo, typeName) 876 body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n' 877 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam) 878 targetLen = 0; 879 for member in typeinfo.elem.findall('.//member'): 880 targetLen = max(targetLen, self.getCParamTypeLength(member)) 881 for member in typeinfo.elem.findall('.//member'): 882 body += self.makeCParamDecl(member, targetLen + 4) 883 body += ';\n' 884 body += '} ' + typeName + ';\n' 885 self.appendSection('struct', body) 886 # 887 # Group (e.g. C "enum" type) generation. 888 # These are concatenated together with other types. 889 def genGroup(self, groupinfo, groupName): 890 OutputGenerator.genGroup(self, groupinfo, groupName) 891 groupElem = groupinfo.elem 892 # See if this group needs min/max/num/padding at end 893 expand = 'expand' in groupElem.keys() 894 if (expand): 895 expandPrefix = groupElem.get('expand') 896 # Prefix 897 body = "\ntypedef enum " + groupName + " {\n" 898 899 # Loop over the nested 'enum' tags. Keep track of the minimum and 900 # maximum numeric values, if they can be determined; but only for 901 # core API enumerants, not extension enumerants. This is inferred 902 # by looking for 'extends' attributes. 903 minName = None 904 for elem in groupElem.findall('enum'): 905 # Convert the value to an integer and use that to track min/max. 906 # Values of form -(number) are accepted but nothing more complex. 907 # Should catch exceptions here for more complex constructs. Not yet. 908 (numVal,strVal) = self.enumToValue(elem, True) 909 name = elem.get('name') 910 body += " " + name + " = " + strVal + ",\n" 911 if (expand and elem.get('extends') is None): 912 if (minName == None): 913 minName = maxName = name 914 minValue = maxValue = numVal 915 elif (numVal < minValue): 916 minName = name 917 minValue = numVal 918 elif (numVal > maxValue): 919 maxName = name 920 maxValue = numVal 921 # Generate min/max value tokens and a range-padding enum. Need some 922 # additional padding to generate correct names... 923 if (expand): 924 body += " " + expandPrefix + "_BEGIN_RANGE = " + minName + ",\n" 925 body += " " + expandPrefix + "_END_RANGE = " + maxName + ",\n" 926 body += " " + expandPrefix + "_RANGE_SIZE = (" + maxName + " - " + minName + " + 1),\n" 927 body += " " + expandPrefix + "_MAX_ENUM = 0x7FFFFFFF\n" 928 # Postfix 929 body += "} " + groupName + ";" 930 if groupElem.get('type') == 'bitmask': 931 section = 'bitmask' 932 else: 933 section = 'group' 934 self.appendSection(section, body) 935 # Enumerant generation 936 # <enum> tags may specify their values in several ways, but are usually 937 # just integers. 938 def genEnum(self, enuminfo, name): 939 OutputGenerator.genEnum(self, enuminfo, name) 940 (numVal,strVal) = self.enumToValue(enuminfo.elem, False) 941 body = '#define ' + name.ljust(33) + ' ' + strVal 942 self.appendSection('enum', body) 943 # 944 # Command generation 945 def genCmd(self, cmdinfo, name): 946 OutputGenerator.genCmd(self, cmdinfo, name) 947 # 948 decls = self.makeCDecls(cmdinfo.elem) 949 self.appendSection('command', decls[0] + '\n') 950 if (self.genOpts.genFuncPointers): 951 self.appendSection('commandPointer', decls[1]) 952 953# DocOutputGenerator - subclass of OutputGenerator. 954# Generates AsciiDoc includes with C-language API interfaces, for reference 955# pages and the Vulkan specification. Similar to COutputGenerator, but 956# each interface is written into a different file as determined by the 957# options, only actual C types are emitted, and none of the boilerplate 958# preprocessor code is emitted. 959# 960# ---- methods ---- 961# DocOutputGenerator(errFile, warnFile, diagFile) - args as for 962# OutputGenerator. Defines additional internal state. 963# ---- methods overriding base class ---- 964# beginFile(genOpts) 965# endFile() 966# beginFeature(interface, emit) 967# endFeature() 968# genType(typeinfo,name) 969# genStruct(typeinfo,name) 970# genGroup(groupinfo,name) 971# genEnum(enuminfo, name) 972# genCmd(cmdinfo) 973class DocOutputGenerator(OutputGenerator): 974 """Generate specified API interfaces in a specific style, such as a C header""" 975 def __init__(self, 976 errFile = sys.stderr, 977 warnFile = sys.stderr, 978 diagFile = sys.stdout): 979 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 980 # 981 def beginFile(self, genOpts): 982 OutputGenerator.beginFile(self, genOpts) 983 def endFile(self): 984 OutputGenerator.endFile(self) 985 def beginFeature(self, interface, emit): 986 # Start processing in superclass 987 OutputGenerator.beginFeature(self, interface, emit) 988 def endFeature(self): 989 # Finish processing in superclass 990 OutputGenerator.endFeature(self) 991 # 992 # Generate an include file 993 # 994 # directory - subdirectory to put file in 995 # basename - base name of the file 996 # contents - contents of the file (Asciidoc boilerplate aside) 997 def writeInclude(self, directory, basename, contents): 998 # Create file 999 filename = self.genOpts.genDirectory + '/' + directory + '/' + basename + '.txt' 1000 self.logMsg('diag', '# Generating include file:', filename) 1001 fp = open(filename, 'w') 1002 # Asciidoc anchor 1003 write('[[{0},{0}]]'.format(basename), file=fp) 1004 write('["source","{basebackend@docbook:c++:cpp}",title=""]', file=fp) 1005 write('------------------------------------------------------------------------------', file=fp) 1006 write(contents, file=fp) 1007 write('------------------------------------------------------------------------------', file=fp) 1008 fp.close() 1009 # 1010 # Type generation 1011 def genType(self, typeinfo, name): 1012 OutputGenerator.genType(self, typeinfo, name) 1013 typeElem = typeinfo.elem 1014 # If the type is a struct type, traverse the imbedded <member> tags 1015 # generating a structure. Otherwise, emit the tag text. 1016 category = typeElem.get('category') 1017 if (category == 'struct' or category == 'union'): 1018 self.genStruct(typeinfo, name) 1019 else: 1020 # Replace <apientry /> tags with an APIENTRY-style string 1021 # (from self.genOpts). Copy other text through unchanged. 1022 # If the resulting text is an empty string, don't emit it. 1023 s = noneStr(typeElem.text) 1024 for elem in typeElem: 1025 if (elem.tag == 'apientry'): 1026 s += self.genOpts.apientry + noneStr(elem.tail) 1027 else: 1028 s += noneStr(elem.text) + noneStr(elem.tail) 1029 if (len(s) > 0): 1030 if (category == 'bitmask'): 1031 self.writeInclude('flags', name, s + '\n') 1032 elif (category == 'enum'): 1033 self.writeInclude('enums', name, s + '\n') 1034 elif (category == 'funcpointer'): 1035 self.writeInclude('funcpointers', name, s+ '\n') 1036 else: 1037 self.logMsg('diag', '# NOT writing include file for type:', 1038 name, 'category: ', category) 1039 else: 1040 self.logMsg('diag', '# NOT writing empty include file for type', name) 1041 # 1042 # Struct (e.g. C "struct" type) generation. 1043 # This is a special case of the <type> tag where the contents are 1044 # interpreted as a set of <member> tags instead of freeform C 1045 # C type declarations. The <member> tags are just like <param> 1046 # tags - they are a declaration of a struct or union member. 1047 # Only simple member declarations are supported (no nested 1048 # structs etc.) 1049 def genStruct(self, typeinfo, typeName): 1050 OutputGenerator.genStruct(self, typeinfo, typeName) 1051 s = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n' 1052 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam) 1053 targetLen = 0; 1054 for member in typeinfo.elem.findall('.//member'): 1055 targetLen = max(targetLen, self.getCParamTypeLength(member)) 1056 for member in typeinfo.elem.findall('.//member'): 1057 s += self.makeCParamDecl(member, targetLen + 4) 1058 s += ';\n' 1059 s += '} ' + typeName + ';' 1060 self.writeInclude('structs', typeName, s) 1061 # 1062 # Group (e.g. C "enum" type) generation. 1063 # These are concatenated together with other types. 1064 def genGroup(self, groupinfo, groupName): 1065 OutputGenerator.genGroup(self, groupinfo, groupName) 1066 groupElem = groupinfo.elem 1067 # See if this group needs min/max/num/padding at end 1068 expand = self.genOpts.expandEnumerants and ('expand' in groupElem.keys()) 1069 if (expand): 1070 expandPrefix = groupElem.get('expand') 1071 # Prefix 1072 s = "typedef enum " + groupName + " {\n" 1073 1074 # Loop over the nested 'enum' tags. Keep track of the minimum and 1075 # maximum numeric values, if they can be determined. 1076 minName = None 1077 for elem in groupElem.findall('enum'): 1078 # Convert the value to an integer and use that to track min/max. 1079 # Values of form -(number) are accepted but nothing more complex. 1080 # Should catch exceptions here for more complex constructs. Not yet. 1081 (numVal,strVal) = self.enumToValue(elem, True) 1082 name = elem.get('name') 1083 s += " " + name + " = " + strVal + ",\n" 1084 if (expand and elem.get('extends') is None): 1085 if (minName == None): 1086 minName = maxName = name 1087 minValue = maxValue = numVal 1088 elif (numVal < minValue): 1089 minName = name 1090 minValue = numVal 1091 elif (numVal > maxValue): 1092 maxName = name 1093 maxValue = numVal 1094 # Generate min/max value tokens and a range-padding enum. Need some 1095 # additional padding to generate correct names... 1096 if (expand): 1097 s += "\n" 1098 s += " " + expandPrefix + "_BEGIN_RANGE = " + minName + ",\n" 1099 s += " " + expandPrefix + "_END_RANGE = " + maxName + ",\n" 1100 s += " " + expandPrefix + "_NUM = (" + maxName + " - " + minName + " + 1),\n" 1101 s += " " + expandPrefix + "_MAX_ENUM = 0x7FFFFFFF\n" 1102 # Postfix 1103 s += "} " + groupName + ";" 1104 self.writeInclude('enums', groupName, s) 1105 # Enumerant generation 1106 # <enum> tags may specify their values in several ways, but are usually 1107 # just integers. 1108 def genEnum(self, enuminfo, name): 1109 OutputGenerator.genEnum(self, enuminfo, name) 1110 (numVal,strVal) = self.enumToValue(enuminfo.elem, False) 1111 s = '#define ' + name.ljust(33) + ' ' + strVal 1112 self.logMsg('diag', '# NOT writing compile-time constant', name) 1113 # self.writeInclude('consts', name, s) 1114 # 1115 # Command generation 1116 def genCmd(self, cmdinfo, name): 1117 OutputGenerator.genCmd(self, cmdinfo, name) 1118 # 1119 decls = self.makeCDecls(cmdinfo.elem) 1120 self.writeInclude('protos', name, decls[0]) 1121 1122# PyOutputGenerator - subclass of OutputGenerator. 1123# Generates Python data structures describing API names. 1124# Similar to DocOutputGenerator, but writes a single 1125# file. 1126# 1127# ---- methods ---- 1128# PyOutputGenerator(errFile, warnFile, diagFile) - args as for 1129# OutputGenerator. Defines additional internal state. 1130# ---- methods overriding base class ---- 1131# beginFile(genOpts) 1132# endFile() 1133# genType(typeinfo,name) 1134# genStruct(typeinfo,name) 1135# genGroup(groupinfo,name) 1136# genEnum(enuminfo, name) 1137# genCmd(cmdinfo) 1138class PyOutputGenerator(OutputGenerator): 1139 """Generate specified API interfaces in a specific style, such as a C header""" 1140 def __init__(self, 1141 errFile = sys.stderr, 1142 warnFile = sys.stderr, 1143 diagFile = sys.stdout): 1144 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 1145 # 1146 def beginFile(self, genOpts): 1147 OutputGenerator.beginFile(self, genOpts) 1148 for dict in [ 'flags', 'enums', 'structs', 'consts', 'enums', 1149 'consts', 'protos', 'funcpointers' ]: 1150 write(dict, '= {}', file=self.outFile) 1151 def endFile(self): 1152 OutputGenerator.endFile(self) 1153 # 1154 # Add a name from the interface 1155 # 1156 # dict - type of name (see beginFile above) 1157 # name - name to add 1158 # value - A serializable Python value for the name 1159 def addName(self, dict, name, value=None): 1160 write(dict + "['" + name + "'] = ", value, file=self.outFile) 1161 # 1162 # Type generation 1163 # For 'struct' or 'union' types, defer to genStruct() to 1164 # add to the dictionary. 1165 # For 'bitmask' types, add the type name to the 'flags' dictionary, 1166 # with the value being the corresponding 'enums' name defining 1167 # the acceptable flag bits. 1168 # For 'enum' types, add the type name to the 'enums' dictionary, 1169 # with the value being '@STOPHERE@' (because this case seems 1170 # never to happen). 1171 # For 'funcpointer' types, add the type name to the 'funcpointers' 1172 # dictionary. 1173 # For 'handle' and 'define' types, add the handle or #define name 1174 # to the 'struct' dictionary, because that's how the spec sources 1175 # tag these types even though they aren't structs. 1176 def genType(self, typeinfo, name): 1177 OutputGenerator.genType(self, typeinfo, name) 1178 typeElem = typeinfo.elem 1179 # If the type is a struct type, traverse the imbedded <member> tags 1180 # generating a structure. Otherwise, emit the tag text. 1181 category = typeElem.get('category') 1182 if (category == 'struct' or category == 'union'): 1183 self.genStruct(typeinfo, name) 1184 else: 1185 # Extract the type name 1186 # (from self.genOpts). Copy other text through unchanged. 1187 # If the resulting text is an empty string, don't emit it. 1188 count = len(noneStr(typeElem.text)) 1189 for elem in typeElem: 1190 count += len(noneStr(elem.text)) + len(noneStr(elem.tail)) 1191 if (count > 0): 1192 if (category == 'bitmask'): 1193 requiredEnum = typeElem.get('requires') 1194 self.addName('flags', name, enquote(requiredEnum)) 1195 elif (category == 'enum'): 1196 # This case never seems to come up! 1197 # @enums C 'enum' name Dictionary of enumerant names 1198 self.addName('enums', name, enquote('@STOPHERE@')) 1199 elif (category == 'funcpointer'): 1200 self.addName('funcpointers', name, None) 1201 elif (category == 'handle' or category == 'define'): 1202 self.addName('structs', name, None) 1203 else: 1204 write('# Unprocessed type:', name, 'category:', category, file=self.outFile) 1205 else: 1206 write('# Unprocessed type:', name, file=self.outFile) 1207 # 1208 # Struct (e.g. C "struct" type) generation. 1209 # 1210 # Add the struct name to the 'structs' dictionary, with the 1211 # value being an ordered list of the struct member names. 1212 def genStruct(self, typeinfo, typeName): 1213 OutputGenerator.genStruct(self, typeinfo, typeName) 1214 1215 members = [member.text for member in typeinfo.elem.findall('.//member/name')] 1216 self.addName('structs', typeName, members) 1217 # 1218 # Group (e.g. C "enum" type) generation. 1219 # These are concatenated together with other types. 1220 # 1221 # Add the enum type name to the 'enums' dictionary, with 1222 # the value being an ordered list of the enumerant names. 1223 # Add each enumerant name to the 'consts' dictionary, with 1224 # the value being the enum type the enumerant is part of. 1225 def genGroup(self, groupinfo, groupName): 1226 OutputGenerator.genGroup(self, groupinfo, groupName) 1227 groupElem = groupinfo.elem 1228 1229 # @enums C 'enum' name Dictionary of enumerant names 1230 # @consts C enumerant/const name Name of corresponding 'enums' key 1231 1232 # Loop over the nested 'enum' tags. Keep track of the minimum and 1233 # maximum numeric values, if they can be determined. 1234 enumerants = [elem.get('name') for elem in groupElem.findall('enum')] 1235 for name in enumerants: 1236 self.addName('consts', name, enquote(groupName)) 1237 self.addName('enums', groupName, enumerants) 1238 # Enumerant generation (compile-time constants) 1239 # 1240 # Add the constant name to the 'consts' dictionary, with the 1241 # value being None to indicate that the constant isn't 1242 # an enumeration value. 1243 def genEnum(self, enuminfo, name): 1244 OutputGenerator.genEnum(self, enuminfo, name) 1245 1246 # @consts C enumerant/const name Name of corresponding 'enums' key 1247 1248 self.addName('consts', name, None) 1249 # 1250 # Command generation 1251 # 1252 # Add the command name to the 'protos' dictionary, with the 1253 # value being an ordered list of the parameter names. 1254 def genCmd(self, cmdinfo, name): 1255 OutputGenerator.genCmd(self, cmdinfo, name) 1256 1257 params = [param.text for param in cmdinfo.elem.findall('param/name')] 1258 self.addName('protos', name, params) 1259 1260# ValidityOutputGenerator - subclass of OutputGenerator. 1261# Generates AsciiDoc includes of valid usage information, for reference 1262# pages and the Vulkan specification. Similar to DocOutputGenerator. 1263# 1264# ---- methods ---- 1265# ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for 1266# OutputGenerator. Defines additional internal state. 1267# ---- methods overriding base class ---- 1268# beginFile(genOpts) 1269# endFile() 1270# beginFeature(interface, emit) 1271# endFeature() 1272# genCmd(cmdinfo) 1273class ValidityOutputGenerator(OutputGenerator): 1274 """Generate specified API interfaces in a specific style, such as a C header""" 1275 def __init__(self, 1276 errFile = sys.stderr, 1277 warnFile = sys.stderr, 1278 diagFile = sys.stdout): 1279 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 1280 1281 def beginFile(self, genOpts): 1282 OutputGenerator.beginFile(self, genOpts) 1283 def endFile(self): 1284 OutputGenerator.endFile(self) 1285 def beginFeature(self, interface, emit): 1286 # Start processing in superclass 1287 OutputGenerator.beginFeature(self, interface, emit) 1288 def endFeature(self): 1289 # Finish processing in superclass 1290 OutputGenerator.endFeature(self) 1291 1292 def makeParameterName(self, name): 1293 return 'pname:' + name 1294 1295 def makeStructName(self, name): 1296 return 'sname:' + name 1297 1298 def makeBaseTypeName(self, name): 1299 return 'basetype:' + name 1300 1301 def makeEnumerationName(self, name): 1302 return 'elink:' + name 1303 1304 def makeEnumerantName(self, name): 1305 return 'ename:' + name 1306 1307 def makeFLink(self, name): 1308 return 'flink:' + name 1309 1310 # 1311 # Generate an include file 1312 # 1313 # directory - subdirectory to put file in 1314 # basename - base name of the file 1315 # contents - contents of the file (Asciidoc boilerplate aside) 1316 def writeInclude(self, directory, basename, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes): 1317 # Create file 1318 filename = self.genOpts.genDirectory + '/' + directory + '/' + basename + '.txt' 1319 self.logMsg('diag', '# Generating include file:', filename) 1320 fp = open(filename, 'w') 1321 # Asciidoc anchor 1322 1323 # Valid Usage 1324 if validity is not None: 1325 write('.Valid Usage', file=fp) 1326 write('*' * 80, file=fp) 1327 write(validity, file=fp, end='') 1328 write('*' * 80, file=fp) 1329 write('', file=fp) 1330 1331 # Host Synchronization 1332 if threadsafety is not None: 1333 write('.Host Synchronization', file=fp) 1334 write('*' * 80, file=fp) 1335 write(threadsafety, file=fp, end='') 1336 write('*' * 80, file=fp) 1337 write('', file=fp) 1338 1339 # Command Properties - contained within a block, to avoid table numbering 1340 if commandpropertiesentry is not None: 1341 write('.Command Properties', file=fp) 1342 write('*' * 80, file=fp) 1343 write('[options="header", width="100%"]', file=fp) 1344 write('|=====================', file=fp) 1345 write('|Command Buffer Levels|Render Pass Scope|Supported Queue Types', file=fp) 1346 write(commandpropertiesentry, file=fp) 1347 write('|=====================', file=fp) 1348 write('*' * 80, file=fp) 1349 write('', file=fp) 1350 1351 # Success Codes - contained within a block, to avoid table numbering 1352 if successcodes is not None or errorcodes is not None: 1353 write('.Return Codes', file=fp) 1354 write('*' * 80, file=fp) 1355 if successcodes is not None: 1356 write('<<fundamentals-successcodes,Success>>::', file=fp) 1357 write(successcodes, file=fp) 1358 if errorcodes is not None: 1359 write('<<fundamentals-errorcodes,Failure>>::', file=fp) 1360 write(errorcodes, file=fp) 1361 write('*' * 80, file=fp) 1362 write('', file=fp) 1363 1364 fp.close() 1365 1366 # 1367 # Check if the parameter passed in is a pointer 1368 def paramIsPointer(self, param): 1369 ispointer = False 1370 paramtype = param.find('type') 1371 if paramtype.tail is not None and '*' in paramtype.tail: 1372 ispointer = True 1373 1374 return ispointer 1375 1376 # 1377 # Check if the parameter passed in is a static array 1378 def paramIsStaticArray(self, param): 1379 if param.find('name').tail is not None: 1380 if param.find('name').tail[0] == '[': 1381 return True 1382 1383 # 1384 # Get the length of a parameter that's been identified as a static array 1385 def staticArrayLength(self, param): 1386 paramname = param.find('name') 1387 paramenumsize = param.find('enum') 1388 1389 if paramenumsize is not None: 1390 return paramenumsize.text 1391 else: 1392 return paramname.tail[1:-1] 1393 1394 # 1395 # Check if the parameter passed in is a pointer to an array 1396 def paramIsArray(self, param): 1397 return param.attrib.get('len') is not None 1398 1399 # 1400 # Get the parent of a handle object 1401 def getHandleParent(self, typename): 1402 types = self.registry.findall("types/type") 1403 for elem in types: 1404 if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename: 1405 return elem.attrib.get('parent') 1406 1407 # 1408 # Check if a parent object is dispatchable or not 1409 def isHandleTypeDispatchable(self, handlename): 1410 handle = self.registry.find("types/type/[name='" + handlename + "'][@category='handle']") 1411 if handle is not None and handle.find('type').text == 'VK_DEFINE_HANDLE': 1412 return True 1413 else: 1414 return False 1415 1416 def isHandleOptional(self, param, params): 1417 1418 # See if the handle is optional 1419 isOptional = False 1420 1421 # Simple, if it's optional, return true 1422 if param.attrib.get('optional') is not None: 1423 return True 1424 1425 # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes. 1426 if param.attrib.get('noautovalidity') is not None: 1427 return True 1428 1429 # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional 1430 if self.paramIsArray(param): 1431 lengths = param.attrib.get('len').split(',') 1432 for length in lengths: 1433 if (length) != 'null-terminated' and (length) != '1': 1434 for otherparam in params: 1435 if otherparam.find('name').text == length: 1436 if otherparam.attrib.get('optional') is not None: 1437 return True 1438 1439 return False 1440 # 1441 # Get the category of a type 1442 def getTypeCategory(self, typename): 1443 types = self.registry.findall("types/type") 1444 for elem in types: 1445 if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename: 1446 return elem.attrib.get('category') 1447 1448 # 1449 # Make a chunk of text for the end of a parameter if it is an array 1450 def makeAsciiDocPreChunk(self, param, params): 1451 paramname = param.find('name') 1452 paramtype = param.find('type') 1453 1454 # General pre-amble. Check optionality and add stuff. 1455 asciidoc = '* ' 1456 1457 if self.paramIsStaticArray(param): 1458 asciidoc += 'Any given element of ' 1459 1460 elif self.paramIsArray(param): 1461 lengths = param.attrib.get('len').split(',') 1462 1463 # Find all the parameters that are called out as optional, so we can document that they might be zero, and the array may be ignored 1464 optionallengths = [] 1465 for length in lengths: 1466 if (length) != 'null-terminated' and (length) != '1': 1467 for otherparam in params: 1468 if otherparam.find('name').text == length: 1469 if otherparam.attrib.get('optional') is not None: 1470 if self.paramIsPointer(otherparam): 1471 optionallengths.append('the value referenced by ' + self.makeParameterName(length)) 1472 else: 1473 optionallengths.append(self.makeParameterName(length)) 1474 1475 # Document that these arrays may be ignored if any of the length values are 0 1476 if len(optionallengths) != 0 or param.attrib.get('optional') is not None: 1477 asciidoc += 'If ' 1478 1479 1480 if len(optionallengths) != 0: 1481 if len(optionallengths) == 1: 1482 1483 asciidoc += optionallengths[0] 1484 asciidoc += ' is ' 1485 1486 else: 1487 asciidoc += ' or '.join(optionallengths) 1488 asciidoc += ' are ' 1489 1490 asciidoc += 'not `0`, ' 1491 1492 if len(optionallengths) != 0 and param.attrib.get('optional') is not None: 1493 asciidoc += 'and ' 1494 1495 if param.attrib.get('optional') is not None: 1496 asciidoc += self.makeParameterName(paramname.text) 1497 asciidoc += ' is not `NULL`, ' 1498 1499 elif param.attrib.get('optional') is not None: 1500 # Don't generate this stub for bitflags 1501 if self.getTypeCategory(paramtype.text) != 'bitmask': 1502 if param.attrib.get('optional').split(',')[0] == 'true': 1503 asciidoc += 'If ' 1504 asciidoc += self.makeParameterName(paramname.text) 1505 asciidoc += ' is not ' 1506 if self.paramIsArray(param) or self.paramIsPointer(param) or self.isHandleTypeDispatchable(paramtype.text): 1507 asciidoc += '`NULL`' 1508 elif self.getTypeCategory(paramtype.text) == 'handle': 1509 asciidoc += 'sname:VK_NULL_HANDLE' 1510 else: 1511 asciidoc += '`0`' 1512 1513 asciidoc += ', ' 1514 1515 return asciidoc 1516 1517 # 1518 # Make the generic asciidoc line chunk portion used for all parameters. 1519 # May return an empty string if nothing to validate. 1520 def createValidationLineForParameterIntroChunk(self, param, params, typetext): 1521 asciidoc = '' 1522 paramname = param.find('name') 1523 paramtype = param.find('type') 1524 1525 asciidoc += self.makeAsciiDocPreChunk(param, params) 1526 1527 asciidoc += self.makeParameterName(paramname.text) 1528 asciidoc += ' must: be ' 1529 1530 if self.paramIsArray(param): 1531 # Arrays. These are hard to get right, apparently 1532 1533 lengths = param.attrib.get('len').split(',') 1534 1535 if (lengths[0]) == 'null-terminated': 1536 asciidoc += 'a null-terminated ' 1537 elif (lengths[0]) == '1': 1538 asciidoc += 'a pointer to ' 1539 else: 1540 asciidoc += 'a pointer to an array of ' 1541 1542 # Handle equations, which are currently denoted with latex 1543 if 'latexmath:' in lengths[0]: 1544 asciidoc += lengths[0] 1545 else: 1546 asciidoc += self.makeParameterName(lengths[0]) 1547 asciidoc += ' ' 1548 1549 for length in lengths[1:]: 1550 if (length) == 'null-terminated': # This should always be the last thing. If it ever isn't for some bizarre reason, then this will need some massaging. 1551 asciidoc += 'null-terminated ' 1552 elif (length) == '1': 1553 asciidoc += 'pointers to ' 1554 else: 1555 asciidoc += 'pointers to arrays of ' 1556 # Handle equations, which are currently denoted with latex 1557 if 'latex:' in length: 1558 asciidoc += length 1559 else: 1560 asciidoc += self.makeParameterName(length) 1561 asciidoc += ' ' 1562 1563 # Void pointers don't actually point at anything - remove the word "to" 1564 if paramtype.text == 'void': 1565 if lengths[-1] == '1': 1566 if len(lengths) > 1: 1567 asciidoc = asciidoc[:-5] # Take care of the extra s added by the post array chunk function. #HACK# 1568 else: 1569 asciidoc = asciidoc[:-4] 1570 else: 1571 # An array of void values is a byte array. 1572 asciidoc += 'byte' 1573 1574 elif paramtype.text == 'char': 1575 # A null terminated array of chars is a string 1576 if lengths[-1] == 'null-terminated': 1577 asciidoc += 'string' 1578 else: 1579 # Else it's just a bunch of chars 1580 asciidoc += 'char value' 1581 elif param.text is not None: 1582 # If a value is "const" that means it won't get modified, so it must be valid going into the function. 1583 if 'const' in param.text: 1584 typecategory = self.getTypeCategory(paramtype.text) 1585 if (typecategory != 'struct' and typecategory != 'union' and typecategory != 'basetype' and typecategory is not None) or not self.isStructAlwaysValid(paramtype.text): 1586 asciidoc += 'valid ' 1587 1588 asciidoc += typetext 1589 1590 # pluralize 1591 if len(lengths) > 1 or (lengths[0] != '1' and lengths[0] != 'null-terminated'): 1592 asciidoc += 's' 1593 1594 elif self.paramIsPointer(param): 1595 # Handle pointers - which are really special case arrays (i.e. they don't have a length) 1596 pointercount = paramtype.tail.count('*') 1597 1598 # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that. 1599 for i in range(0, pointercount): 1600 asciidoc += 'a pointer to ' 1601 1602 if paramtype.text == 'void': 1603 # If there's only one pointer, it's optional, and it doesn't point at anything in particular - we don't need any language. 1604 if pointercount == 1 and param.attrib.get('optional') is not None: 1605 return '' # early return 1606 else: 1607 # Pointer to nothing in particular - delete the " to " portion 1608 asciidoc = asciidoc[:-4] 1609 else: 1610 # Add an article for English semantic win 1611 asciidoc += 'a ' 1612 1613 # If a value is "const" that means it won't get modified, so it must be valid going into the function. 1614 if param.text is not None and paramtype.text != 'void': 1615 if 'const' in param.text: 1616 asciidoc += 'valid ' 1617 1618 asciidoc += typetext 1619 1620 else: 1621 # Non-pointer, non-optional things must be valid 1622 asciidoc += 'a valid ' 1623 asciidoc += typetext 1624 1625 if asciidoc != '': 1626 asciidoc += '\n' 1627 1628 # Add additional line for non-optional bitmasks 1629 if self.getTypeCategory(paramtype.text) == 'bitmask': 1630 if param.attrib.get('optional') is None: 1631 asciidoc += '* ' 1632 if self.paramIsArray(param): 1633 asciidoc += 'Each element of ' 1634 asciidoc += 'pname:' 1635 asciidoc += paramname.text 1636 asciidoc += ' mustnot: be `0`' 1637 asciidoc += '\n' 1638 1639 return asciidoc 1640 1641 def makeAsciiDocLineForParameter(self, param, params, typetext): 1642 if param.attrib.get('noautovalidity') is not None: 1643 return '' 1644 asciidoc = self.createValidationLineForParameterIntroChunk(param, params, typetext) 1645 1646 return asciidoc 1647 1648 # Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance) 1649 def isStructAlwaysValid(self, structname): 1650 1651 struct = self.registry.find("types/type[@name='" + structname + "']") 1652 1653 params = struct.findall('member') 1654 validity = struct.find('validity') 1655 1656 if validity is not None: 1657 return False 1658 1659 for param in params: 1660 paramname = param.find('name') 1661 paramtype = param.find('type') 1662 typecategory = self.getTypeCategory(paramtype.text) 1663 1664 if paramname.text == 'pNext': 1665 return False 1666 1667 if paramname.text == 'sType': 1668 return False 1669 1670 if paramtype.text == 'void' or paramtype.text == 'char' or self.paramIsArray(param) or self.paramIsPointer(param): 1671 if self.makeAsciiDocLineForParameter(param, params, '') != '': 1672 return False 1673 elif typecategory == 'handle' or typecategory == 'enum' or typecategory == 'bitmask' or param.attrib.get('returnedonly') == 'true': 1674 return False 1675 elif typecategory == 'struct' or typecategory == 'union': 1676 if self.isStructAlwaysValid(paramtype.text) is False: 1677 return False 1678 1679 return True 1680 1681 # 1682 # Make an entire asciidoc line for a given parameter 1683 def createValidationLineForParameter(self, param, params, typecategory): 1684 asciidoc = '' 1685 paramname = param.find('name') 1686 paramtype = param.find('type') 1687 1688 if paramtype.text == 'void' or paramtype.text == 'char': 1689 # Chars and void are special cases - needs care inside the generator functions 1690 # A null-terminated char array is a string, else it's chars. 1691 # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular 1692 asciidoc += self.makeAsciiDocLineForParameter(param, params, '') 1693 elif typecategory == 'bitmask': 1694 bitsname = paramtype.text.replace('Flags', 'FlagBits') 1695 if self.registry.find("enums[@name='" + bitsname + "']") is None: 1696 asciidoc += '* ' 1697 asciidoc += self.makeParameterName(paramname.text) 1698 asciidoc += ' must: be `0`' 1699 asciidoc += '\n' 1700 else: 1701 if self.paramIsArray(param): 1702 asciidoc += self.makeAsciiDocLineForParameter(param, params, 'combinations of ' + self.makeEnumerationName(bitsname) + ' value') 1703 else: 1704 asciidoc += self.makeAsciiDocLineForParameter(param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values') 1705 elif typecategory == 'handle': 1706 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' handle') 1707 elif typecategory == 'enum': 1708 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeEnumerationName(paramtype.text) + ' value') 1709 elif typecategory == 'struct': 1710 if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(paramtype.text): 1711 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' structure') 1712 elif typecategory == 'union': 1713 if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(paramtype.text): 1714 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' union') 1715 elif self.paramIsArray(param) or self.paramIsPointer(param): 1716 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeBaseTypeName(paramtype.text) + ' value') 1717 1718 return asciidoc 1719 1720 # 1721 # Make an asciidoc validity entry for a handle's parent object 1722 def makeAsciiDocHandleParent(self, param, params): 1723 asciidoc = '' 1724 paramname = param.find('name') 1725 paramtype = param.find('type') 1726 1727 # Deal with handle parents 1728 handleparent = self.getHandleParent(paramtype.text) 1729 if handleparent is not None: 1730 parentreference = None 1731 for otherparam in params: 1732 if otherparam.find('type').text == handleparent: 1733 parentreference = otherparam.find('name').text 1734 if parentreference is not None: 1735 asciidoc += '* ' 1736 1737 if self.isHandleOptional(param, params): 1738 if self.paramIsArray(param): 1739 asciidoc += 'Each element of ' 1740 asciidoc += self.makeParameterName(paramname.text) 1741 asciidoc += ' that is a valid handle' 1742 else: 1743 asciidoc += 'If ' 1744 asciidoc += self.makeParameterName(paramname.text) 1745 asciidoc += ' is a valid handle, it' 1746 else: 1747 if self.paramIsArray(param): 1748 asciidoc += 'Each element of ' 1749 asciidoc += self.makeParameterName(paramname.text) 1750 asciidoc += ' must: have been created, allocated or retrieved from ' 1751 asciidoc += self.makeParameterName(parentreference) 1752 1753 asciidoc += '\n' 1754 return asciidoc 1755 1756 # 1757 # Generate an asciidoc validity line for the sType value of a struct 1758 def makeStructureType(self, blockname, param): 1759 asciidoc = '* ' 1760 paramname = param.find('name') 1761 paramtype = param.find('type') 1762 1763 asciidoc += self.makeParameterName(paramname.text) 1764 asciidoc += ' must: be ' 1765 1766 structuretype = '' 1767 for elem in re.findall(r'(([A-Z][a-z]+)|([A-Z][A-Z]+))', blockname): 1768 if elem[0] == 'Vk': 1769 structuretype += 'VK_STRUCTURE_TYPE_' 1770 else: 1771 structuretype += elem[0].upper() 1772 structuretype += '_' 1773 1774 asciidoc += self.makeEnumerantName(structuretype[:-1]) 1775 asciidoc += '\n' 1776 1777 return asciidoc 1778 1779 # 1780 # Generate an asciidoc validity line for the pNext value of a struct 1781 def makeStructureExtensionPointer(self, param): 1782 asciidoc = '* ' 1783 paramname = param.find('name') 1784 paramtype = param.find('type') 1785 1786 asciidoc += self.makeParameterName(paramname.text) 1787 1788 validextensionstructs = param.attrib.get('validextensionstructs') 1789 if validextensionstructs is None: 1790 asciidoc += ' must: be `NULL`' 1791 else: 1792 extensionstructs = validextensionstructs.split(',') 1793 asciidoc += ' must: point to one of ' + extensionstructs[:-1].join(', ') + ' or ' + extensionstructs[-1] + 'if the extension that introduced them is enabled ' 1794 1795 asciidoc += '\n' 1796 1797 return asciidoc 1798 1799 # 1800 # Generate all the valid usage information for a given struct or command 1801 def makeValidUsageStatements(self, cmd, blockname, params, usages): 1802 # Start the asciidoc block for this 1803 asciidoc = '' 1804 1805 handles = [] 1806 anyparentedhandlesoptional = False 1807 parentdictionary = {} 1808 arraylengths = set() 1809 for param in params: 1810 paramname = param.find('name') 1811 paramtype = param.find('type') 1812 1813 # Get the type's category 1814 typecategory = self.getTypeCategory(paramtype.text) 1815 1816 # Generate language to independently validate a parameter 1817 if paramtype.text == 'VkStructureType' and paramname.text == 'sType': 1818 asciidoc += self.makeStructureType(blockname, param) 1819 elif paramtype.text == 'void' and paramname.text == 'pNext': 1820 asciidoc += self.makeStructureExtensionPointer(param) 1821 else: 1822 asciidoc += self.createValidationLineForParameter(param, params, typecategory) 1823 1824 # Ensure that any parenting is properly validated, and list that a handle was found 1825 if typecategory == 'handle': 1826 # Don't detect a parent for return values! 1827 if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text): 1828 parent = self.getHandleParent(paramtype.text) 1829 if parent is not None: 1830 handles.append(param) 1831 1832 # If any param is optional, it affects the output 1833 if self.isHandleOptional(param, params): 1834 anyparentedhandlesoptional = True 1835 1836 # Find the first dispatchable parent 1837 ancestor = parent 1838 while ancestor is not None and not self.isHandleTypeDispatchable(ancestor): 1839 ancestor = self.getHandleParent(ancestor) 1840 1841 # If one was found, add this parameter to the parent dictionary 1842 if ancestor is not None: 1843 if ancestor not in parentdictionary: 1844 parentdictionary[ancestor] = [] 1845 1846 if self.paramIsArray(param): 1847 parentdictionary[ancestor].append('the elements of ' + self.makeParameterName(paramname.text)) 1848 else: 1849 parentdictionary[ancestor].append(self.makeParameterName(paramname.text)) 1850 1851 # Get the array length for this parameter 1852 arraylength = param.attrib.get('len') 1853 if arraylength is not None: 1854 for onelength in arraylength.split(','): 1855 arraylengths.add(onelength) 1856 1857 # For any vkQueue* functions, there might be queue type data 1858 if 'vkQueue' in blockname: 1859 # The queue type must be valid 1860 queuetypes = cmd.attrib.get('queues') 1861 if queuetypes is not None: 1862 queuebits = [] 1863 for queuetype in re.findall(r'([^,]+)', queuetypes): 1864 queuebits.append(queuetype.replace('_',' ')) 1865 1866 asciidoc += '* ' 1867 asciidoc += 'The pname:queue must: support ' 1868 if len(queuebits) == 1: 1869 asciidoc += queuebits[0] 1870 else: 1871 asciidoc += (', ').join(queuebits[:-1]) 1872 asciidoc += ' or ' 1873 asciidoc += queuebits[-1] 1874 asciidoc += ' operations' 1875 asciidoc += '\n' 1876 1877 if 'vkCmd' in blockname: 1878 # The commandBuffer parameter must be being recorded 1879 asciidoc += '* ' 1880 asciidoc += 'pname:commandBuffer must: be in the recording state' 1881 asciidoc += '\n' 1882 1883 # The queue type must be valid 1884 queuetypes = cmd.attrib.get('queues') 1885 queuebits = [] 1886 for queuetype in re.findall(r'([^,]+)', queuetypes): 1887 queuebits.append(queuetype.replace('_',' ')) 1888 1889 asciidoc += '* ' 1890 asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support ' 1891 if len(queuebits) == 1: 1892 asciidoc += queuebits[0] 1893 else: 1894 asciidoc += (', ').join(queuebits[:-1]) 1895 asciidoc += ' or ' 1896 asciidoc += queuebits[-1] 1897 asciidoc += ' operations' 1898 asciidoc += '\n' 1899 1900 # Must be called inside/outside a renderpass appropriately 1901 renderpass = cmd.attrib.get('renderpass') 1902 1903 if renderpass != 'both': 1904 asciidoc += '* This command must: only be called ' 1905 asciidoc += renderpass 1906 asciidoc += ' of a render pass instance' 1907 asciidoc += '\n' 1908 1909 # Must be in the right level command buffer 1910 cmdbufferlevel = cmd.attrib.get('cmdbufferlevel') 1911 1912 if cmdbufferlevel != 'primary,secondary': 1913 asciidoc += '* pname:commandBuffer must: be a ' 1914 asciidoc += cmdbufferlevel 1915 asciidoc += ' sname:VkCommandBuffer' 1916 asciidoc += '\n' 1917 1918 # Any non-optional arraylengths should specify they must be greater than 0 1919 for param in params: 1920 paramname = param.find('name') 1921 1922 for arraylength in arraylengths: 1923 if paramname.text == arraylength and param.attrib.get('optional') is None: 1924 # Get all the array dependencies 1925 arrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']") 1926 1927 # Get all the optional array dependencies, including those not generating validity for some reason 1928 optionalarrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']") 1929 optionalarrays.extend(cmd.findall("param/[@len='" + arraylength + "'][@noautovalidity='true']")) 1930 1931 asciidoc += '* ' 1932 1933 # Allow lengths to be arbitrary if all their dependents are optional 1934 if len(optionalarrays) == len(arrays) and len(optionalarrays) != 0: 1935 asciidoc += 'If ' 1936 if len(optionalarrays) > 1: 1937 asciidoc += 'any of ' 1938 1939 for array in optionalarrays[:-1]: 1940 asciidoc += self.makeParameterName(optionalarrays.find('name').text) 1941 asciidoc += ', ' 1942 1943 if len(optionalarrays) > 1: 1944 asciidoc += 'and ' 1945 asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text) 1946 asciidoc += ' are ' 1947 else: 1948 asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text) 1949 asciidoc += ' is ' 1950 1951 asciidoc += 'not `NULL`, ' 1952 1953 if self.paramIsPointer(param): 1954 asciidoc += 'the value referenced by ' 1955 else: 1956 asciidoc += 'the value of ' 1957 1958 elif self.paramIsPointer(param): 1959 asciidoc += 'The value referenced by ' 1960 else: 1961 asciidoc += 'The value of ' 1962 1963 asciidoc += self.makeParameterName(arraylength) 1964 asciidoc += ' must: be greater than `0`' 1965 asciidoc += '\n' 1966 1967 # Find the parents of all objects referenced in this command 1968 for param in handles: 1969 asciidoc += self.makeAsciiDocHandleParent(param, params) 1970 1971 # Find the common ancestors of objects 1972 noancestorscount = 0 1973 while noancestorscount < len(parentdictionary): 1974 noancestorscount = 0 1975 oldparentdictionary = parentdictionary.copy() 1976 for parent in oldparentdictionary.items(): 1977 ancestor = self.getHandleParent(parent[0]) 1978 1979 while ancestor is not None and ancestor not in parentdictionary: 1980 ancestor = self.getHandleParent(ancestor) 1981 1982 if ancestor is not None: 1983 parentdictionary[ancestor] += parentdictionary.pop(parent[0]) 1984 else: 1985 # No ancestors possible - so count it up 1986 noancestorscount += 1 1987 1988 # Add validation language about common ancestors 1989 for parent in parentdictionary.items(): 1990 if len(parent[1]) > 1: 1991 parentlanguage = '* ' 1992 1993 parentlanguage += 'Each of ' 1994 parentlanguage += ", ".join(parent[1][:-1]) 1995 parentlanguage += ' and ' 1996 parentlanguage += parent[1][-1] 1997 if anyparentedhandlesoptional is True: 1998 parentlanguage += ' that are valid handles' 1999 parentlanguage += ' must: have been created, allocated or retrieved from the same ' 2000 parentlanguage += self.makeStructName(parent[0]) 2001 parentlanguage += '\n' 2002 2003 # Capitalize and add to the main language 2004 asciidoc += parentlanguage 2005 2006 # Add in any plain-text validation language that's in the xml 2007 for usage in usages: 2008 asciidoc += '* ' 2009 asciidoc += usage.text 2010 asciidoc += '\n' 2011 2012 # In case there's nothing to report, return None 2013 if asciidoc == '': 2014 return None 2015 # Delimit the asciidoc block 2016 return asciidoc 2017 2018 def makeThreadSafetyBlock(self, cmd, paramtext): 2019 """Generate C function pointer typedef for <command> Element""" 2020 paramdecl = '' 2021 2022 # For any vkCmd* functions, the commandBuffer parameter must be being recorded 2023 if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name'): 2024 paramdecl += '* ' 2025 paramdecl += 'The sname:VkCommandPool that pname:commandBuffer was created from' 2026 paramdecl += '\n' 2027 2028 # Find and add any parameters that are thread unsafe 2029 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]") 2030 if (explicitexternsyncparams is not None): 2031 for param in explicitexternsyncparams: 2032 externsyncattribs = param.attrib.get('externsync') 2033 paramname = param.find('name') 2034 for externsyncattrib in externsyncattribs.split(','): 2035 paramdecl += '* ' 2036 paramdecl += 'Host access to ' 2037 if externsyncattrib == 'true': 2038 if self.paramIsArray(param): 2039 paramdecl += 'each member of ' + self.makeParameterName(paramname.text) 2040 elif self.paramIsPointer(param): 2041 paramdecl += 'the object referenced by ' + self.makeParameterName(paramname.text) 2042 else: 2043 paramdecl += self.makeParameterName(paramname.text) 2044 else: 2045 paramdecl += 'pname:' 2046 paramdecl += externsyncattrib 2047 paramdecl += ' must: be externally synchronized\n' 2048 2049 # Find and add any "implicit" parameters that are thread unsafe 2050 implicitexternsyncparams = cmd.find('implicitexternsyncparams') 2051 if (implicitexternsyncparams is not None): 2052 for elem in implicitexternsyncparams: 2053 paramdecl += '* ' 2054 paramdecl += 'Host access to ' 2055 paramdecl += elem.text 2056 paramdecl += ' must: be externally synchronized\n' 2057 2058 if (paramdecl == ''): 2059 return None 2060 else: 2061 return paramdecl 2062 2063 def makeCommandPropertiesTableEntry(self, cmd, name): 2064 2065 if 'vkCmd' in name: 2066 # Must be called inside/outside a renderpass appropriately 2067 cmdbufferlevel = cmd.attrib.get('cmdbufferlevel') 2068 cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(',')) 2069 2070 renderpass = cmd.attrib.get('renderpass') 2071 renderpass = renderpass.capitalize() 2072 2073 queues = cmd.attrib.get('queues') 2074 queues = (' + \n').join(queues.upper().split(',')) 2075 2076 return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues 2077 elif 'vkQueue' in name: 2078 # Must be called inside/outside a renderpass appropriately 2079 2080 queues = cmd.attrib.get('queues') 2081 if queues is None: 2082 queues = 'Any' 2083 else: 2084 queues = (' + \n').join(queues.upper().split(',')) 2085 2086 return '|-|-|' + queues 2087 2088 return None 2089 2090 def makeSuccessCodes(self, cmd, name): 2091 2092 successcodes = cmd.attrib.get('successcodes') 2093 if successcodes is not None: 2094 2095 successcodeentry = '' 2096 successcodes = successcodes.split(',') 2097 return '* ' + '\n* '.join(successcodes) 2098 2099 return None 2100 2101 def makeErrorCodes(self, cmd, name): 2102 2103 errorcodes = cmd.attrib.get('errorcodes') 2104 if errorcodes is not None: 2105 2106 errorcodeentry = '' 2107 errorcodes = errorcodes.split(',') 2108 return '* ' + '\n* '.join(errorcodes) 2109 2110 return None 2111 2112 # 2113 # Command generation 2114 def genCmd(self, cmdinfo, name): 2115 OutputGenerator.genCmd(self, cmdinfo, name) 2116 # 2117 # Get all thh parameters 2118 params = cmdinfo.elem.findall('param') 2119 usages = cmdinfo.elem.findall('validity/usage') 2120 2121 validity = self.makeValidUsageStatements(cmdinfo.elem, name, params, usages) 2122 threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param') 2123 commandpropertiesentry = self.makeCommandPropertiesTableEntry(cmdinfo.elem, name) 2124 successcodes = self.makeSuccessCodes(cmdinfo.elem, name) 2125 errorcodes = self.makeErrorCodes(cmdinfo.elem, name) 2126 2127 self.writeInclude('validity/protos', name, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes) 2128 2129 # 2130 # Struct Generation 2131 def genStruct(self, typeinfo, typename): 2132 OutputGenerator.genStruct(self, typeinfo, typename) 2133 2134 # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information. 2135 if typeinfo.elem.attrib.get('returnedonly') is None: 2136 params = typeinfo.elem.findall('member') 2137 usages = typeinfo.elem.findall('validity/usage') 2138 2139 validity = self.makeValidUsageStatements(typeinfo.elem, typename, params, usages) 2140 threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member') 2141 2142 self.writeInclude('validity/structs', typename, validity, threadsafety, None, None, None) 2143 else: 2144 # Still generate files for return only structs, in case this state changes later 2145 self.writeInclude('validity/structs', typename, None, None, None, None, None) 2146 2147 # 2148 # Type Generation 2149 def genType(self, typeinfo, typename): 2150 OutputGenerator.genType(self, typeinfo, typename) 2151 2152 category = typeinfo.elem.get('category') 2153 if (category == 'struct' or category == 'union'): 2154 self.genStruct(typeinfo, typename) 2155 2156# HostSynchronizationOutputGenerator - subclass of OutputGenerator. 2157# Generates AsciiDoc includes of the externsync parameter table for the 2158# fundamentals chapter of the Vulkan specification. Similar to 2159# DocOutputGenerator. 2160# 2161# ---- methods ---- 2162# HostSynchronizationOutputGenerator(errFile, warnFile, diagFile) - args as for 2163# OutputGenerator. Defines additional internal state. 2164# ---- methods overriding base class ---- 2165# genCmd(cmdinfo) 2166class HostSynchronizationOutputGenerator(OutputGenerator): 2167 # Generate Host Synchronized Parameters in a table at the top of the spec 2168 def __init__(self, 2169 errFile = sys.stderr, 2170 warnFile = sys.stderr, 2171 diagFile = sys.stdout): 2172 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 2173 2174 threadsafety = {'parameters': '', 'parameterlists': '', 'implicit': ''} 2175 2176 def makeParameterName(self, name): 2177 return 'pname:' + name 2178 2179 def makeFLink(self, name): 2180 return 'flink:' + name 2181 2182 # 2183 # Generate an include file 2184 # 2185 # directory - subdirectory to put file in 2186 # basename - base name of the file 2187 # contents - contents of the file (Asciidoc boilerplate aside) 2188 def writeInclude(self): 2189 2190 if self.threadsafety['parameters'] is not None: 2191 # Create file 2192 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/parameters.txt' 2193 self.logMsg('diag', '# Generating include file:', filename) 2194 fp = open(filename, 'w') 2195 2196 # Host Synchronization 2197 write('.Externally Synchronized Parameters', file=fp) 2198 write('*' * 80, file=fp) 2199 write(self.threadsafety['parameters'], file=fp, end='') 2200 write('*' * 80, file=fp) 2201 write('', file=fp) 2202 2203 if self.threadsafety['parameterlists'] is not None: 2204 # Create file 2205 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/parameterlists.txt' 2206 self.logMsg('diag', '# Generating include file:', filename) 2207 fp = open(filename, 'w') 2208 2209 # Host Synchronization 2210 write('.Externally Synchronized Parameter Lists', file=fp) 2211 write('*' * 80, file=fp) 2212 write(self.threadsafety['parameterlists'], file=fp, end='') 2213 write('*' * 80, file=fp) 2214 write('', file=fp) 2215 2216 if self.threadsafety['implicit'] is not None: 2217 # Create file 2218 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/implicit.txt' 2219 self.logMsg('diag', '# Generating include file:', filename) 2220 fp = open(filename, 'w') 2221 2222 # Host Synchronization 2223 write('.Implicit Externally Synchronized Parameters', file=fp) 2224 write('*' * 80, file=fp) 2225 write(self.threadsafety['implicit'], file=fp, end='') 2226 write('*' * 80, file=fp) 2227 write('', file=fp) 2228 2229 fp.close() 2230 2231 # 2232 # Check if the parameter passed in is a pointer to an array 2233 def paramIsArray(self, param): 2234 return param.attrib.get('len') is not None 2235 2236 # Check if the parameter passed in is a pointer 2237 def paramIsPointer(self, param): 2238 ispointer = False 2239 paramtype = param.find('type') 2240 if paramtype.tail is not None and '*' in paramtype.tail: 2241 ispointer = True 2242 2243 return ispointer 2244 2245 # Turn the "name[].member[]" notation into plain English. 2246 def makeThreadDereferenceHumanReadable(self, dereference): 2247 matches = re.findall(r"[\w]+[^\w]*",dereference) 2248 stringval = '' 2249 for match in reversed(matches): 2250 if '->' in match or '.' in match: 2251 stringval += 'member of ' 2252 if '[]' in match: 2253 stringval += 'each element of ' 2254 2255 stringval += 'the ' 2256 stringval += self.makeParameterName(re.findall(r"[\w]+",match)[0]) 2257 stringval += ' ' 2258 2259 stringval += 'parameter' 2260 2261 return stringval[0].upper() + stringval[1:] 2262 2263 def makeThreadSafetyBlocks(self, cmd, paramtext): 2264 protoname = cmd.find('proto/name').text 2265 2266 # Find and add any parameters that are thread unsafe 2267 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]") 2268 if (explicitexternsyncparams is not None): 2269 for param in explicitexternsyncparams: 2270 externsyncattribs = param.attrib.get('externsync') 2271 paramname = param.find('name') 2272 for externsyncattrib in externsyncattribs.split(','): 2273 2274 tempstring = '* ' 2275 if externsyncattrib == 'true': 2276 if self.paramIsArray(param): 2277 tempstring += 'Each element of the ' 2278 elif self.paramIsPointer(param): 2279 tempstring += 'The object referenced by the ' 2280 else: 2281 tempstring += 'The ' 2282 2283 tempstring += self.makeParameterName(paramname.text) 2284 tempstring += ' parameter' 2285 2286 else: 2287 tempstring += self.makeThreadDereferenceHumanReadable(externsyncattrib) 2288 2289 tempstring += ' in ' 2290 tempstring += self.makeFLink(protoname) 2291 tempstring += '\n' 2292 2293 2294 if ' element of ' in tempstring: 2295 self.threadsafety['parameterlists'] += tempstring 2296 else: 2297 self.threadsafety['parameters'] += tempstring 2298 2299 2300 # Find and add any "implicit" parameters that are thread unsafe 2301 implicitexternsyncparams = cmd.find('implicitexternsyncparams') 2302 if (implicitexternsyncparams is not None): 2303 for elem in implicitexternsyncparams: 2304 self.threadsafety['implicit'] += '* ' 2305 self.threadsafety['implicit'] += elem.text[0].upper() 2306 self.threadsafety['implicit'] += elem.text[1:] 2307 self.threadsafety['implicit'] += ' in ' 2308 self.threadsafety['implicit'] += self.makeFLink(protoname) 2309 self.threadsafety['implicit'] += '\n' 2310 2311 2312 # For any vkCmd* functions, the commandBuffer parameter must be being recorded 2313 if protoname is not None and 'vkCmd' in protoname: 2314 self.threadsafety['implicit'] += '* ' 2315 self.threadsafety['implicit'] += 'The sname:VkCommandPool that pname:commandBuffer was allocated from, in ' 2316 self.threadsafety['implicit'] += self.makeFLink(protoname) 2317 2318 self.threadsafety['implicit'] += '\n' 2319 2320 # 2321 # Command generation 2322 def genCmd(self, cmdinfo, name): 2323 OutputGenerator.genCmd(self, cmdinfo, name) 2324 # 2325 # Get all thh parameters 2326 params = cmdinfo.elem.findall('param') 2327 usages = cmdinfo.elem.findall('validity/usage') 2328 2329 self.makeThreadSafetyBlocks(cmdinfo.elem, 'param') 2330 2331 self.writeInclude() 2332 2333# ThreadOutputGenerator - subclass of OutputGenerator. 2334# Generates Thread checking framework 2335# 2336# ---- methods ---- 2337# ThreadOutputGenerator(errFile, warnFile, diagFile) - args as for 2338# OutputGenerator. Defines additional internal state. 2339# ---- methods overriding base class ---- 2340# beginFile(genOpts) 2341# endFile() 2342# beginFeature(interface, emit) 2343# endFeature() 2344# genType(typeinfo,name) 2345# genStruct(typeinfo,name) 2346# genGroup(groupinfo,name) 2347# genEnum(enuminfo, name) 2348# genCmd(cmdinfo) 2349class ThreadOutputGenerator(OutputGenerator): 2350 """Generate specified API interfaces in a specific style, such as a C header""" 2351 # This is an ordered list of sections in the header file. 2352 TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', 2353 'group', 'bitmask', 'funcpointer', 'struct'] 2354 ALL_SECTIONS = TYPE_SECTIONS + ['command'] 2355 def __init__(self, 2356 errFile = sys.stderr, 2357 warnFile = sys.stderr, 2358 diagFile = sys.stdout): 2359 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 2360 # Internal state - accumulators for different inner block text 2361 self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) 2362 self.intercepts = [] 2363 2364 # Check if the parameter passed in is a pointer to an array 2365 def paramIsArray(self, param): 2366 return param.attrib.get('len') is not None 2367 2368 # Check if the parameter passed in is a pointer 2369 def paramIsPointer(self, param): 2370 ispointer = False 2371 for elem in param: 2372 #write('paramIsPointer '+elem.text, file=sys.stderr) 2373 #write('elem.tag '+elem.tag, file=sys.stderr) 2374 #if (elem.tail is None): 2375 # write('elem.tail is None', file=sys.stderr) 2376 #else: 2377 # write('elem.tail '+elem.tail, file=sys.stderr) 2378 if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail: 2379 ispointer = True 2380 # write('is pointer', file=sys.stderr) 2381 return ispointer 2382 def makeThreadUseBlock(self, cmd, functionprefix): 2383 """Generate C function pointer typedef for <command> Element""" 2384 paramdecl = '' 2385 thread_check_dispatchable_objects = [ 2386 "VkCommandBuffer", 2387 "VkDevice", 2388 "VkInstance", 2389 "VkQueue", 2390 ] 2391 thread_check_nondispatchable_objects = [ 2392 "VkBuffer", 2393 "VkBufferView", 2394 "VkCommandPool", 2395 "VkDescriptorPool", 2396 "VkDescriptorSetLayout", 2397 "VkDeviceMemory", 2398 "VkEvent", 2399 "VkFence", 2400 "VkFramebuffer", 2401 "VkImage", 2402 "VkImageView", 2403 "VkPipeline", 2404 "VkPipelineCache", 2405 "VkPipelineLayout", 2406 "VkQueryPool", 2407 "VkRenderPass", 2408 "VkSampler", 2409 "VkSemaphore", 2410 "VkShaderModule", 2411 ] 2412 2413 # Find and add any parameters that are thread unsafe 2414 params = cmd.findall('param') 2415 for param in params: 2416 paramname = param.find('name') 2417 if False: # self.paramIsPointer(param): 2418 paramdecl += ' // not watching use of pointer ' + paramname.text + '\n' 2419 else: 2420 externsync = param.attrib.get('externsync') 2421 if externsync == 'true': 2422 if self.paramIsArray(param): 2423 paramdecl += ' for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n' 2424 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + '[index]);\n' 2425 paramdecl += ' }\n' 2426 else: 2427 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + ');\n' 2428 elif (param.attrib.get('externsync')): 2429 if self.paramIsArray(param): 2430 # Externsync can list pointers to arrays of members to synchronize 2431 paramdecl += ' for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n' 2432 for member in externsync.split(","): 2433 # Replace first empty [] in member name with index 2434 element = member.replace('[]','[index]',1) 2435 if '[]' in element: 2436 # Replace any second empty [] in element name with 2437 # inner array index based on mapping array names like 2438 # "pSomeThings[]" to "someThingCount" array size. 2439 # This could be more robust by mapping a param member 2440 # name to a struct type and "len" attribute. 2441 limit = element[0:element.find('s[]')] + 'Count' 2442 dotp = limit.rfind('.p') 2443 limit = limit[0:dotp+1] + limit[dotp+2:dotp+3].lower() + limit[dotp+3:] 2444 paramdecl += ' for(int index2=0;index2<'+limit+';index2++)' 2445 element = element.replace('[]','[index2]') 2446 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + element + ');\n' 2447 paramdecl += ' }\n' 2448 else: 2449 # externsync can list members to synchronize 2450 for member in externsync.split(","): 2451 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + member + ');\n' 2452 else: 2453 paramtype = param.find('type') 2454 if paramtype is not None: 2455 paramtype = paramtype.text 2456 else: 2457 paramtype = 'None' 2458 if paramtype in thread_check_dispatchable_objects or paramtype in thread_check_nondispatchable_objects: 2459 if self.paramIsArray(param) and ('pPipelines' != paramname.text): 2460 paramdecl += ' for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n' 2461 paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + '[index]);\n' 2462 paramdecl += ' }\n' 2463 elif not self.paramIsPointer(param): 2464 # Pointer params are often being created. 2465 # They are not being read from. 2466 paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + ');\n' 2467 explicitexternsyncparams = cmd.findall("param[@externsync]") 2468 if (explicitexternsyncparams is not None): 2469 for param in explicitexternsyncparams: 2470 externsyncattrib = param.attrib.get('externsync') 2471 paramname = param.find('name') 2472 paramdecl += '// Host access to ' 2473 if externsyncattrib == 'true': 2474 if self.paramIsArray(param): 2475 paramdecl += 'each member of ' + paramname.text 2476 elif self.paramIsPointer(param): 2477 paramdecl += 'the object referenced by ' + paramname.text 2478 else: 2479 paramdecl += paramname.text 2480 else: 2481 paramdecl += externsyncattrib 2482 paramdecl += ' must be externally synchronized\n' 2483 2484 # Find and add any "implicit" parameters that are thread unsafe 2485 implicitexternsyncparams = cmd.find('implicitexternsyncparams') 2486 if (implicitexternsyncparams is not None): 2487 for elem in implicitexternsyncparams: 2488 paramdecl += ' // ' 2489 paramdecl += elem.text 2490 paramdecl += ' must be externally synchronized between host accesses\n' 2491 2492 if (paramdecl == ''): 2493 return None 2494 else: 2495 return paramdecl 2496 def beginFile(self, genOpts): 2497 OutputGenerator.beginFile(self, genOpts) 2498 # C-specific 2499 # 2500 # Multiple inclusion protection & C++ wrappers. 2501 if (genOpts.protectFile and self.genOpts.filename): 2502 headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename)) 2503 write('#ifndef', headerSym, file=self.outFile) 2504 write('#define', headerSym, '1', file=self.outFile) 2505 self.newline() 2506 write('#ifdef __cplusplus', file=self.outFile) 2507 write('extern "C" {', file=self.outFile) 2508 write('#endif', file=self.outFile) 2509 self.newline() 2510 # 2511 # User-supplied prefix text, if any (list of strings) 2512 if (genOpts.prefixText): 2513 for s in genOpts.prefixText: 2514 write(s, file=self.outFile) 2515 def endFile(self): 2516 # C-specific 2517 # Finish C++ wrapper and multiple inclusion protection 2518 self.newline() 2519 # record intercepted procedures 2520 write('// intercepts', file=self.outFile) 2521 write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile) 2522 write('\n'.join(self.intercepts), file=self.outFile) 2523 write('};\n', file=self.outFile) 2524 self.newline() 2525 write('#ifdef __cplusplus', file=self.outFile) 2526 write('}', file=self.outFile) 2527 write('#endif', file=self.outFile) 2528 if (self.genOpts.protectFile and self.genOpts.filename): 2529 self.newline() 2530 write('#endif', file=self.outFile) 2531 # Finish processing in superclass 2532 OutputGenerator.endFile(self) 2533 def beginFeature(self, interface, emit): 2534 #write('// starting beginFeature', file=self.outFile) 2535 # Start processing in superclass 2536 OutputGenerator.beginFeature(self, interface, emit) 2537 # C-specific 2538 # Accumulate includes, defines, types, enums, function pointer typedefs, 2539 # end function prototypes separately for this feature. They're only 2540 # printed in endFeature(). 2541 self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) 2542 #write('// ending beginFeature', file=self.outFile) 2543 def endFeature(self): 2544 # C-specific 2545 # Actually write the interface to the output file. 2546 #write('// starting endFeature', file=self.outFile) 2547 if (self.emit): 2548 self.newline() 2549 if (self.genOpts.protectFeature): 2550 write('#ifndef', self.featureName, file=self.outFile) 2551 # If type declarations are needed by other features based on 2552 # this one, it may be necessary to suppress the ExtraProtect, 2553 # or move it below the 'for section...' loop. 2554 #write('// endFeature looking at self.featureExtraProtect', file=self.outFile) 2555 if (self.featureExtraProtect != None): 2556 write('#ifdef', self.featureExtraProtect, file=self.outFile) 2557 #write('#define', self.featureName, '1', file=self.outFile) 2558 for section in self.TYPE_SECTIONS: 2559 #write('// endFeature writing section'+section, file=self.outFile) 2560 contents = self.sections[section] 2561 if contents: 2562 write('\n'.join(contents), file=self.outFile) 2563 self.newline() 2564 #write('// endFeature looking at self.sections[command]', file=self.outFile) 2565 if (self.sections['command']): 2566 write('\n'.join(self.sections['command']), end='', file=self.outFile) 2567 self.newline() 2568 if (self.featureExtraProtect != None): 2569 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) 2570 if (self.genOpts.protectFeature): 2571 write('#endif /*', self.featureName, '*/', file=self.outFile) 2572 # Finish processing in superclass 2573 OutputGenerator.endFeature(self) 2574 #write('// ending endFeature', file=self.outFile) 2575 # 2576 # Append a definition to the specified section 2577 def appendSection(self, section, text): 2578 # self.sections[section].append('SECTION: ' + section + '\n') 2579 self.sections[section].append(text) 2580 # 2581 # Type generation 2582 def genType(self, typeinfo, name): 2583 pass 2584 # 2585 # Struct (e.g. C "struct" type) generation. 2586 # This is a special case of the <type> tag where the contents are 2587 # interpreted as a set of <member> tags instead of freeform C 2588 # C type declarations. The <member> tags are just like <param> 2589 # tags - they are a declaration of a struct or union member. 2590 # Only simple member declarations are supported (no nested 2591 # structs etc.) 2592 def genStruct(self, typeinfo, typeName): 2593 OutputGenerator.genStruct(self, typeinfo, typeName) 2594 body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n' 2595 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam) 2596 for member in typeinfo.elem.findall('.//member'): 2597 body += self.makeCParamDecl(member, self.genOpts.alignFuncParam) 2598 body += ';\n' 2599 body += '} ' + typeName + ';\n' 2600 self.appendSection('struct', body) 2601 # 2602 # Group (e.g. C "enum" type) generation. 2603 # These are concatenated together with other types. 2604 def genGroup(self, groupinfo, groupName): 2605 pass 2606 # Enumerant generation 2607 # <enum> tags may specify their values in several ways, but are usually 2608 # just integers. 2609 def genEnum(self, enuminfo, name): 2610 pass 2611 # 2612 # Command generation 2613 def genCmd(self, cmdinfo, name): 2614 special_functions = [ 2615 'vkGetDeviceProcAddr', 2616 'vkGetInstanceProcAddr', 2617 'vkCreateDevice', 2618 'vkDestroyDevice', 2619 'vkCreateInstance', 2620 'vkDestroyInstance', 2621 'vkEnumerateInstanceLayerProperties', 2622 'vkEnumerateInstanceExtensionProperties', 2623 'vkAllocateCommandBuffers', 2624 'vkFreeCommandBuffers', 2625 'vkCreateDebugReportCallbackEXT', 2626 'vkDestroyDebugReportCallbackEXT', 2627 ] 2628 if name in special_functions: 2629 self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name) ] 2630 return 2631 if "KHR" in name: 2632 self.appendSection('command', '// TODO - not wrapping KHR function ' + name) 2633 return 2634 # Determine first if this function needs to be intercepted 2635 startthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'start') 2636 if startthreadsafety is None: 2637 return 2638 finishthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'finish') 2639 # record that the function will be intercepted 2640 if (self.featureExtraProtect != None): 2641 self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ] 2642 self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name) ] 2643 if (self.featureExtraProtect != None): 2644 self.intercepts += [ '#endif' ] 2645 2646 OutputGenerator.genCmd(self, cmdinfo, name) 2647 # 2648 decls = self.makeCDecls(cmdinfo.elem) 2649 self.appendSection('command', '') 2650 self.appendSection('command', decls[0][:-1]) 2651 self.appendSection('command', '{') 2652 # setup common to call wrappers 2653 # first parameter is always dispatchable 2654 dispatchable_type = cmdinfo.elem.find('param/type').text 2655 dispatchable_name = cmdinfo.elem.find('param/name').text 2656 self.appendSection('command', ' dispatch_key key = get_dispatch_key('+dispatchable_name+');') 2657 self.appendSection('command', ' layer_data *my_data = get_my_data_ptr(key, layer_data_map);') 2658 if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]: 2659 self.appendSection('command', ' VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table;') 2660 else: 2661 self.appendSection('command', ' VkLayerDispatchTable *pTable = my_data->device_dispatch_table;') 2662 # Declare result variable, if any. 2663 resulttype = cmdinfo.elem.find('proto/type') 2664 if (resulttype != None and resulttype.text == 'void'): 2665 resulttype = None 2666 if (resulttype != None): 2667 self.appendSection('command', ' ' + resulttype.text + ' result;') 2668 assignresult = 'result = ' 2669 else: 2670 assignresult = '' 2671 2672 self.appendSection('command', str(startthreadsafety)) 2673 params = cmdinfo.elem.findall('param/name') 2674 paramstext = ','.join([str(param.text) for param in params]) 2675 API = cmdinfo.elem.attrib.get('name').replace('vk','pTable->',1) 2676 self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');') 2677 self.appendSection('command', str(finishthreadsafety)) 2678 # Return result variable, if any. 2679 if (resulttype != None): 2680 self.appendSection('command', ' return result;') 2681 self.appendSection('command', '}') 2682 2683# ParamCheckerOutputGenerator - subclass of OutputGenerator. 2684# Generates param checker layer code. 2685# 2686# ---- methods ---- 2687# ParamCheckerOutputGenerator(errFile, warnFile, diagFile) - args as for 2688# OutputGenerator. Defines additional internal state. 2689# ---- methods overriding base class ---- 2690# beginFile(genOpts) 2691# endFile() 2692# beginFeature(interface, emit) 2693# endFeature() 2694# genType(typeinfo,name) 2695# genStruct(typeinfo,name) 2696# genGroup(groupinfo,name) 2697# genEnum(enuminfo, name) 2698# genCmd(cmdinfo) 2699class ParamCheckerOutputGenerator(OutputGenerator): 2700 """Generate ParamChecker code based on XML element attributes""" 2701 # This is an ordered list of sections in the header file. 2702 ALL_SECTIONS = ['command'] 2703 def __init__(self, 2704 errFile = sys.stderr, 2705 warnFile = sys.stderr, 2706 diagFile = sys.stdout): 2707 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 2708 self.INDENT_SPACES = 4 2709 # Commands to ignore 2710 self.blacklist = [ 2711 'vkGetInstanceProcAddr', 2712 'vkGetDeviceProcAddr', 2713 'vkEnumerateInstanceLayerProperties', 2714 'vkEnumerateInstanceExtensionsProperties', 2715 'vkEnumerateDeviceLayerProperties', 2716 'vkEnumerateDeviceExtensionsProperties', 2717 'vkCreateDebugReportCallbackEXT', 2718 'vkDebugReportMessageEXT'] 2719 # Internal state - accumulators for different inner block text 2720 self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) 2721 self.structNames = [] # List of Vulkan struct typenames 2722 self.stypes = [] # Values from the VkStructureType enumeration 2723 self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType 2724 self.commands = [] # List of CommandData records for all Vulkan commands 2725 self.structMembers = [] # List of StructMemberData records for all Vulkan structs 2726 self.validatedStructs = set() # Set of structs containing members that require validation 2727 # Named tuples to store struct and command data 2728 self.StructType = namedtuple('StructType', ['name', 'value']) 2729 self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isstaticarray', 'isoptional', 'iscount', 'len', 'extstructs', 'cdecl']) 2730 self.CommandData = namedtuple('CommandData', ['name', 'params', 'cdecl']) 2731 self.StructMemberData = namedtuple('StructMemberData', ['name', 'members']) 2732 # 2733 def incIndent(self, indent): 2734 inc = ' ' * self.INDENT_SPACES 2735 if indent: 2736 return indent + inc 2737 return inc 2738 # 2739 def decIndent(self, indent): 2740 if indent and (len(indent) > self.INDENT_SPACES): 2741 return indent[:-self.INDENT_SPACES] 2742 return '' 2743 # 2744 def beginFile(self, genOpts): 2745 OutputGenerator.beginFile(self, genOpts) 2746 # C-specific 2747 # 2748 # User-supplied prefix text, if any (list of strings) 2749 if (genOpts.prefixText): 2750 for s in genOpts.prefixText: 2751 write(s, file=self.outFile) 2752 # 2753 # Multiple inclusion protection & C++ wrappers. 2754 if (genOpts.protectFile and self.genOpts.filename): 2755 headerSym = re.sub('\.h', '_H', os.path.basename(self.genOpts.filename)).upper() 2756 write('#ifndef', headerSym, file=self.outFile) 2757 write('#define', headerSym, '1', file=self.outFile) 2758 self.newline() 2759 # 2760 # Headers 2761 write('#include "vulkan/vulkan.h"', file=self.outFile) 2762 write('#include "vk_layer_extension_utils.h"', file=self.outFile) 2763 write('#include "parameter_validation_utils.h"', file=self.outFile) 2764 # 2765 # Macros 2766 self.newline() 2767 write('#ifndef UNUSED_PARAMETER', file=self.outFile) 2768 write('#define UNUSED_PARAMETER(x) (void)(x)', file=self.outFile) 2769 write('#endif // UNUSED_PARAMETER', file=self.outFile) 2770 def endFile(self): 2771 # C-specific 2772 # Finish C++ wrapper and multiple inclusion protection 2773 self.newline() 2774 if (self.genOpts.protectFile and self.genOpts.filename): 2775 self.newline() 2776 write('#endif', file=self.outFile) 2777 # Finish processing in superclass 2778 OutputGenerator.endFile(self) 2779 def beginFeature(self, interface, emit): 2780 # Start processing in superclass 2781 OutputGenerator.beginFeature(self, interface, emit) 2782 # C-specific 2783 # Accumulate includes, defines, types, enums, function pointer typedefs, 2784 # end function prototypes separately for this feature. They're only 2785 # printed in endFeature(). 2786 self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) 2787 self.structNames = [] 2788 self.stypes = [] 2789 self.structTypes = dict() 2790 self.commands = [] 2791 self.structMembers = [] 2792 self.validatedStructs = set() 2793 def endFeature(self): 2794 # C-specific 2795 # Actually write the interface to the output file. 2796 if (self.emit): 2797 self.newline() 2798 # If type declarations are needed by other features based on 2799 # this one, it may be necessary to suppress the ExtraProtect, 2800 # or move it below the 'for section...' loop. 2801 if (self.featureExtraProtect != None): 2802 write('#ifdef', self.featureExtraProtect, file=self.outFile) 2803 # Generate the struct member checking code from the captured data 2804 self.prepareStructMemberData() 2805 self.processStructMemberData() 2806 # Generate the command parameter checking code from the captured data 2807 self.processCmdData() 2808 if (self.sections['command']): 2809 if (self.genOpts.protectProto): 2810 write(self.genOpts.protectProto, 2811 self.genOpts.protectProtoStr, file=self.outFile) 2812 write('\n'.join(self.sections['command']), end='', file=self.outFile) 2813 if (self.featureExtraProtect != None): 2814 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) 2815 else: 2816 self.newline() 2817 # Finish processing in superclass 2818 OutputGenerator.endFeature(self) 2819 # 2820 # Append a definition to the specified section 2821 def appendSection(self, section, text): 2822 # self.sections[section].append('SECTION: ' + section + '\n') 2823 self.sections[section].append(text) 2824 # 2825 # Type generation 2826 def genType(self, typeinfo, name): 2827 OutputGenerator.genType(self, typeinfo, name) 2828 typeElem = typeinfo.elem 2829 # If the type is a struct type, traverse the imbedded <member> tags 2830 # generating a structure. Otherwise, emit the tag text. 2831 category = typeElem.get('category') 2832 if (category == 'struct' or category == 'union'): 2833 self.structNames.append(name) 2834 self.genStruct(typeinfo, name) 2835 # 2836 # Struct parameter check generation. 2837 # This is a special case of the <type> tag where the contents are 2838 # interpreted as a set of <member> tags instead of freeform C 2839 # C type declarations. The <member> tags are just like <param> 2840 # tags - they are a declaration of a struct or union member. 2841 # Only simple member declarations are supported (no nested 2842 # structs etc.) 2843 def genStruct(self, typeinfo, typeName): 2844 OutputGenerator.genStruct(self, typeinfo, typeName) 2845 members = typeinfo.elem.findall('.//member') 2846 # 2847 # Iterate over members once to get length parameters for arrays 2848 lens = set() 2849 for member in members: 2850 len = self.getLen(member) 2851 if len: 2852 lens.add(len) 2853 # 2854 # Generate member info 2855 membersInfo = [] 2856 for member in members: 2857 # Get the member's type and name 2858 info = self.getTypeNameTuple(member) 2859 type = info[0] 2860 name = info[1] 2861 stypeValue = '' 2862 # Process VkStructureType 2863 if type == 'VkStructureType': 2864 # Extract the required struct type value from the comments 2865 # embedded in the original text defining the 'typeinfo' element 2866 rawXml = etree.tostring(typeinfo.elem).decode('ascii') 2867 result = re.search(r'VK_STRUCTURE_TYPE_\w+', rawXml) 2868 if result: 2869 value = result.group(0) 2870 # Make sure value is valid 2871 #if value not in self.stypes: 2872 # print('WARNING: {} is not part of the VkStructureType enumeration [{}]'.format(value, typeName)) 2873 else: 2874 value = '<ERROR>' 2875 # Store the required type value 2876 self.structTypes[typeName] = self.StructType(name=name, value=value) 2877 # 2878 # Store pointer/array/string info 2879 # Check for parameter name in lens set 2880 iscount = False 2881 if name in lens: 2882 iscount = True 2883 # The pNext members are not tagged as optional, but are treated as 2884 # optional for parameter NULL checks. Static array members 2885 # are also treated as optional to skip NULL pointer validation, as 2886 # they won't be NULL. 2887 isstaticarray = self.paramIsStaticArray(member) 2888 isoptional = False 2889 if self.paramIsOptional(member) or (name == 'pNext') or (isstaticarray): 2890 isoptional = True 2891 membersInfo.append(self.CommandParam(type=type, name=name, 2892 ispointer=self.paramIsPointer(member), 2893 isstaticarray=isstaticarray, 2894 isoptional=isoptional, 2895 iscount=iscount, 2896 len=self.getLen(member), 2897 extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None, 2898 cdecl=self.makeCParamDecl(member, 0))) 2899 self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo)) 2900 # 2901 # Capture group (e.g. C "enum" type) info to be used for 2902 # param check code generation. 2903 # These are concatenated together with other types. 2904 def genGroup(self, groupinfo, groupName): 2905 OutputGenerator.genGroup(self, groupinfo, groupName) 2906 if groupName == 'VkStructureType': 2907 groupElem = groupinfo.elem 2908 for elem in groupElem.findall('enum'): 2909 name = elem.get('name') 2910 self.stypes.append(name) 2911 # 2912 # Capture command parameter info to be used for param 2913 # check code generation. 2914 def genCmd(self, cmdinfo, name): 2915 OutputGenerator.genCmd(self, cmdinfo, name) 2916 if name not in self.blacklist: 2917 params = cmdinfo.elem.findall('param') 2918 # Get list of array lengths 2919 lens = set() 2920 for param in params: 2921 len = self.getLen(param) 2922 if len: 2923 lens.add(len) 2924 # Get param info 2925 paramsInfo = [] 2926 for param in params: 2927 paramInfo = self.getTypeNameTuple(param) 2928 # Check for parameter name in lens set 2929 iscount = False 2930 if paramInfo[1] in lens: 2931 iscount = True 2932 paramsInfo.append(self.CommandParam(type=paramInfo[0], name=paramInfo[1], 2933 ispointer=self.paramIsPointer(param), 2934 isstaticarray=self.paramIsStaticArray(param), 2935 isoptional=self.paramIsOptional(param), 2936 iscount=iscount, 2937 len=self.getLen(param), 2938 extstructs=None, 2939 cdecl=self.makeCParamDecl(param, 0))) 2940 self.commands.append(self.CommandData(name=name, params=paramsInfo, cdecl=self.makeCDecls(cmdinfo.elem)[0])) 2941 # 2942 # Check if the parameter passed in is a pointer 2943 def paramIsPointer(self, param): 2944 ispointer = 0 2945 paramtype = param.find('type') 2946 if (paramtype.tail is not None) and ('*' in paramtype.tail): 2947 ispointer = paramtype.tail.count('*') 2948 elif paramtype.text[:4] == 'PFN_': 2949 # Treat function pointer typedefs as a pointer to a single value 2950 ispointer = 1 2951 return ispointer 2952 # 2953 # Check if the parameter passed in is a static array 2954 def paramIsStaticArray(self, param): 2955 isstaticarray = 0 2956 paramname = param.find('name') 2957 if (paramname.tail is not None) and ('[' in paramname.tail): 2958 isstaticarray = paramname.tail.count('[') 2959 return isstaticarray 2960 # 2961 # Check if the parameter passed in is optional 2962 # Returns a list of Boolean values for comma separated len attributes (len='false,true') 2963 def paramIsOptional(self, param): 2964 # See if the handle is optional 2965 isoptional = False 2966 # Simple, if it's optional, return true 2967 optString = param.attrib.get('optional') 2968 if optString: 2969 if optString == 'true': 2970 isoptional = True 2971 elif ',' in optString: 2972 opts = [] 2973 for opt in optString.split(','): 2974 val = opt.strip() 2975 if val == 'true': 2976 opts.append(True) 2977 elif val == 'false': 2978 opts.append(False) 2979 else: 2980 print('Unrecognized len attribute value',val) 2981 isoptional = opts 2982 return isoptional 2983 # 2984 # Retrieve the value of the len tag 2985 def getLen(self, param): 2986 result = None 2987 len = param.attrib.get('len') 2988 if len and len != 'null-terminated': 2989 # For string arrays, 'len' can look like 'count,null-terminated', 2990 # indicating that we have a null terminated array of strings. We 2991 # strip the null-terminated from the 'len' field and only return 2992 # the parameter specifying the string count 2993 if 'null-terminated' in len: 2994 result = len.split(',')[0] 2995 else: 2996 result = len 2997 return result 2998 # 2999 # Retrieve the type and name for a parameter 3000 def getTypeNameTuple(self, param): 3001 type = '' 3002 name = '' 3003 for elem in param: 3004 if elem.tag == 'type': 3005 type = noneStr(elem.text) 3006 elif elem.tag == 'name': 3007 name = noneStr(elem.text) 3008 return (type, name) 3009 # 3010 # Find a named parameter in a parameter list 3011 def getParamByName(self, params, name): 3012 for param in params: 3013 if param.name == name: 3014 return param 3015 return None 3016 # 3017 # Get the length paramater record for the specified parameter name 3018 def getLenParam(self, params, name): 3019 lenParam = None 3020 if name: 3021 if '->' in name: 3022 # The count is obtained by dereferencing a member of a struct parameter 3023 lenParam = self.CommandParam(name=name, iscount=True, ispointer=False, isoptional=False, type=None, len=None, isstaticarray=None, extstructs=None, cdecl=None) 3024 elif 'latexmath' in name: 3025 result = re.search('mathit\{(\w+)\}', name) 3026 lenParam = self.getParamByName(params, result.group(1)) 3027 elif '/' in name: 3028 # Len specified as an equation such as dataSize/4 3029 lenParam = self.getParamByName(params, name.split('/')[0]) 3030 else: 3031 lenParam = self.getParamByName(params, name) 3032 return lenParam 3033 # 3034 # Convert a vulkan.h command declaration into a parameter_validation.h definition 3035 def getCmdDef(self, cmd): 3036 # 3037 # Strip the trailing ';' and split into individual lines 3038 lines = cmd.cdecl[:-1].split('\n') 3039 # Replace Vulkan prototype 3040 lines[0] = 'static VkBool32 parameter_validation_' + cmd.name + '(' 3041 # Replace the first argument with debug_report_data, when the first 3042 # argument is a handle (not vkCreateInstance) 3043 reportData = ' debug_report_data*'.ljust(self.genOpts.alignFuncParam) + 'report_data,' 3044 if cmd.name != 'vkCreateInstance': 3045 lines[1] = reportData 3046 else: 3047 lines.insert(1, reportData) 3048 return '\n'.join(lines) 3049 # 3050 # Generate the code to check for a NULL dereference before calling the 3051 # validation function 3052 def genCheckedLengthCall(self, indent, name, expr): 3053 count = name.count('->') 3054 if count: 3055 checkedExpr = '' 3056 localIndent = indent 3057 elements = name.split('->') 3058 # Open the if expression blocks 3059 for i in range(0, count): 3060 checkedExpr += localIndent + 'if ({} != NULL) {{\n'.format('->'.join(elements[0:i+1])) 3061 localIndent = self.incIndent(localIndent) 3062 # Add the validation expression 3063 checkedExpr += localIndent + expr 3064 # Close the if blocks 3065 for i in range(0, count): 3066 localIndent = self.decIndent(localIndent) 3067 checkedExpr += localIndent + '}\n' 3068 return checkedExpr 3069 # No if statements were required 3070 return indent + expr 3071 # 3072 # Generate the parameter checking code 3073 def genFuncBody(self, indent, name, values, valuePrefix, variablePrefix, structName): 3074 funcBody = '' 3075 unused = [] 3076 for value in values: 3077 checkExpr = '' # Code to check the current parameter 3078 # 3079 # Check for NULL pointers, ignore the inout count parameters that 3080 # will be validated with their associated array 3081 if (value.ispointer or value.isstaticarray) and not value.iscount: 3082 # 3083 # Generate the full name of the value, which will be printed in 3084 # the error message, by adding the variable prefix to the 3085 # value name 3086 valueDisplayName = '(std::string({}) + std::string("{}")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}"'.format(value.name) 3087 # 3088 # Parameters for function argument generation 3089 req = 'VK_TRUE' # Paramerter can be NULL 3090 cpReq = 'VK_TRUE' # Count pointer can be NULL 3091 cvReq = 'VK_TRUE' # Count value can be 0 3092 lenParam = None 3093 # 3094 # Generate required/optional parameter strings for the pointer and count values 3095 if value.isoptional: 3096 req = 'VK_FALSE' 3097 if value.len: 3098 # The parameter is an array with an explicit count parameter 3099 lenParam = self.getLenParam(values, value.len) 3100 if not lenParam: print(value.len) 3101 if lenParam.ispointer: 3102 # Count parameters that are pointers are inout 3103 if type(lenParam.isoptional) is list: 3104 if lenParam.isoptional[0]: 3105 cpReq = 'VK_FALSE' 3106 if lenParam.isoptional[1]: 3107 cvReq = 'VK_FALSE' 3108 else: 3109 if lenParam.isoptional: 3110 cpReq = 'VK_FALSE' 3111 else: 3112 if lenParam.isoptional: 3113 cvReq = 'VK_FALSE' 3114 # 3115 # If this is a pointer to a struct with an sType field, verify the type 3116 if value.type in self.structTypes: 3117 stype = self.structTypes[value.type] 3118 if lenParam: 3119 # This is an array 3120 if lenParam.ispointer: 3121 # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required 3122 checkExpr = 'skipCall |= validate_struct_type_array(report_data, {}, "{ln}", {dn}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {}, {});\n'.format(name, cpReq, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, sv=stype.value, pf=valuePrefix) 3123 else: 3124 checkExpr = 'skipCall |= validate_struct_type_array(report_data, {}, "{ln}", {dn}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {});\n'.format(name, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, sv=stype.value, pf=valuePrefix) 3125 else: 3126 checkExpr = 'skipCall |= validate_struct_type(report_data, {}, {}, "{sv}", {}{vn}, {sv}, {});\n'.format(name, valueDisplayName, valuePrefix, req, vn=value.name, sv=stype.value) 3127 elif value.name == 'pNext': 3128 # We need to ignore VkDeviceCreateInfo and VkInstanceCreateInfo, as the loader manipulates them in a way that is not documented in vk.xml 3129 if not structName in ['VkDeviceCreateInfo', 'VkInstanceCreateInfo']: 3130 # Generate an array of acceptable VkStructureType values for pNext 3131 extStructCount = 0 3132 extStructVar = 'NULL' 3133 extStructNames = 'NULL' 3134 if value.extstructs: 3135 structs = value.extstructs.split(',') 3136 checkExpr = 'const VkStructureType allowedStructs[] = {' + ', '.join([self.structTypes[s].value for s in structs]) + '};\n' + indent 3137 extStructCount = 'ARRAY_SIZE(allowedStructs)' 3138 extStructVar = 'allowedStructs' 3139 extStructNames = '"' + ', '.join(structs) + '"' 3140 checkExpr += 'skipCall |= validate_struct_pnext(report_data, {}, {}, {}, {}{vn}, {}, {});\n'.format(name, valueDisplayName, extStructNames, valuePrefix, extStructCount, extStructVar, vn=value.name) 3141 else: 3142 if lenParam: 3143 # This is an array 3144 if lenParam.ispointer: 3145 # If count and array parameters are optional, there 3146 # will be no validation 3147 if req == 'VK_TRUE' or cpReq == 'VK_TRUE' or cvReq == 'VK_TRUE': 3148 # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required 3149 checkExpr = 'skipCall |= validate_array(report_data, {}, "{ln}", {dn}, {pf}{ln}, {pf}{vn}, {}, {}, {});\n'.format(name, cpReq, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, pf=valuePrefix) 3150 else: 3151 # If count and array parameters are optional, there 3152 # will be no validation 3153 if req == 'VK_TRUE' or cvReq == 'VK_TRUE': 3154 funcName = 'validate_array' if value.type != 'char' else 'validate_string_array' 3155 checkExpr = 'skipCall |= {}(report_data, {}, "{ln}", {dn}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format(funcName, name, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, pf=valuePrefix) 3156 elif not value.isoptional: 3157 # Function pointers need a reinterpret_cast to void* 3158 if value.type[:4] == 'PFN_': 3159 checkExpr = 'skipCall |= validate_required_pointer(report_data, {}, {}, reinterpret_cast<const void*>({}{vn}));\n'.format(name, valueDisplayName, valuePrefix, vn=value.name) 3160 else: 3161 checkExpr = 'skipCall |= validate_required_pointer(report_data, {}, {}, {}{vn});\n'.format(name, valueDisplayName, valuePrefix, vn=value.name) 3162 # 3163 # If this is a pointer to a struct, see if it contains members 3164 # that need to be checked 3165 if value.type in self.validatedStructs: 3166 if checkExpr: 3167 checkExpr += '\n' + indent 3168 # 3169 # The name prefix used when reporting an error with a struct member (eg. the 'pCreateInfor->' in 'pCreateInfo->sType') 3170 prefix = '(std::string({}) + std::string("{}->")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}->"'.format(value.name) 3171 checkExpr += 'skipCall |= parameter_validation_{}(report_data, {}, {}, {}{});\n'.format(value.type, name, prefix, valuePrefix, value.name) 3172 elif value.type in self.validatedStructs: 3173 # The name prefix used when reporting an error with a struct member (eg. the 'pCreateInfor->' in 'pCreateInfo->sType') 3174 prefix = '(std::string({}) + std::string("{}.")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}."'.format(value.name) 3175 checkExpr += 'skipCall |= parameter_validation_{}(report_data, {}, {}, &({}{}));\n'.format(value.type, name, prefix, valuePrefix, value.name) 3176 # 3177 # Append the parameter check to the function body for the current command 3178 if checkExpr: 3179 funcBody += '\n' 3180 if lenParam and ('->' in lenParam.name): 3181 # Add checks to ensure the validation call does not dereference a NULL pointer to obtain the count 3182 funcBody += self.genCheckedLengthCall(indent, lenParam.name, checkExpr) 3183 else: 3184 funcBody += indent + checkExpr 3185 elif not value.iscount: 3186 # The parameter is not checked (counts will be checked with 3187 # their associated array) 3188 unused.append(value.name) 3189 return funcBody, unused 3190 # 3191 # Post-process the collected struct member data to create a list of structs 3192 # with members that need to be validated 3193 def prepareStructMemberData(self): 3194 for struct in self.structMembers: 3195 for member in struct.members: 3196 if not member.iscount: 3197 lenParam = self.getLenParam(struct.members, member.len) 3198 # The sType needs to be validated 3199 # An required array/count needs to be validated 3200 # A required pointer needs to be validated 3201 validated = False 3202 if member.type in self.structTypes: 3203 validated = True 3204 elif member.ispointer and lenParam: # This is an array 3205 # Make sure len is not optional 3206 if lenParam.ispointer: 3207 if not lenParam.isoptional[0] or not lenParam.isoptional[1] or not member.isoptional: 3208 validated = True 3209 else: 3210 if not lenParam.isoptional or not member.isoptional: 3211 validated = True 3212 elif member.ispointer and not member.isoptional: 3213 validated = True 3214 # 3215 if validated: 3216 self.validatedStructs.add(struct.name) 3217 # Second pass to check for struct members that are structs 3218 # requiring validation 3219 for member in struct.members: 3220 if member.type in self.validatedStructs: 3221 self.validatedStructs.add(struct.name) 3222 # 3223 # Generate the struct member check code from the captured data 3224 def processStructMemberData(self): 3225 indent = self.incIndent(None) 3226 for struct in self.structMembers: 3227 # The string returned by genFuncBody will be nested in an if check 3228 # for a NULL pointer, so needs its indent incremented 3229 funcBody, unused = self.genFuncBody(self.incIndent(indent), 'pFuncName', struct.members, 'pStruct->', 'pVariableName', struct.name) 3230 if funcBody: 3231 cmdDef = 'static VkBool32 parameter_validation_{}(\n'.format(struct.name) 3232 cmdDef += ' debug_report_data*'.ljust(self.genOpts.alignFuncParam) + ' report_data,\n' 3233 cmdDef += ' const char*'.ljust(self.genOpts.alignFuncParam) + ' pFuncName,\n' 3234 cmdDef += ' const char*'.ljust(self.genOpts.alignFuncParam) + ' pVariableName,\n' 3235 cmdDef += ' const {}*'.format(struct.name).ljust(self.genOpts.alignFuncParam) + ' pStruct)\n' 3236 cmdDef += '{\n' 3237 cmdDef += indent + 'VkBool32 skipCall = VK_FALSE;\n' 3238 cmdDef += '\n' 3239 cmdDef += indent + 'if (pStruct != NULL) {' 3240 cmdDef += funcBody 3241 cmdDef += indent +'}\n' 3242 cmdDef += '\n' 3243 cmdDef += indent + 'return skipCall;\n' 3244 cmdDef += '}\n' 3245 self.appendSection('command', cmdDef) 3246 # 3247 # Generate the command param check code from the captured data 3248 def processCmdData(self): 3249 indent = self.incIndent(None) 3250 for command in self.commands: 3251 cmdBody, unused = self.genFuncBody(indent, '"{}"'.format(command.name), command.params, '', None, None) 3252 if cmdBody: 3253 cmdDef = self.getCmdDef(command) + '\n' 3254 cmdDef += '{\n' 3255 # Process unused parameters 3256 # Ignore the first dispatch handle parameter, which is not 3257 # processed by parameter_validation (except for vkCreateInstance, which 3258 # does not have a handle as its first parameter) 3259 startIndex = 1 3260 if command.name == 'vkCreateInstance': 3261 startIndex = 0 3262 for name in unused[startIndex:]: 3263 cmdDef += indent + 'UNUSED_PARAMETER({});\n'.format(name) 3264 if len(unused) > 1: 3265 cmdDef += '\n' 3266 cmdDef += indent + 'VkBool32 skipCall = VK_FALSE;\n' 3267 cmdDef += cmdBody 3268 cmdDef += '\n' 3269 cmdDef += indent + 'return skipCall;\n' 3270 cmdDef += '}\n' 3271 self.appendSection('command', cmdDef) 3272