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