1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2021 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7"""Types and classes for manipulating an API registry.""" 8 9import copy 10import re 11import sys 12import xml.etree.ElementTree as etree 13from collections import defaultdict, namedtuple 14from generator import OutputGenerator, GeneratorOptions, write 15import pdb 16 17def apiNameMatch(str, supported): 18 """Return whether a required api name matches a pattern specified for an 19 XML <feature> 'api' attribute or <extension> 'supported' attribute. 20 21 - str - api name such as 'vulkan' or 'openxr' 22 - supported - comma-separated list of XML API names""" 23 24 return (str is not None and str in supported.split(',')) 25 26 27def matchAPIProfile(api, profile, elem): 28 """Return whether an API and profile 29 being generated matches an element's profile 30 31 - api - string naming the API to match 32 - profile - string naming the profile to match 33 - elem - Element which (may) have 'api' and 'profile' 34 attributes to match to. 35 36 If a tag is not present in the Element, the corresponding API 37 or profile always matches. 38 39 Otherwise, the tag must exactly match the API or profile. 40 41 Thus, if 'profile' = core: 42 43 - `<remove>` with no attribute will match 44 - `<remove profile="core">` will match 45 - `<remove profile="compatibility">` will not match 46 47 Possible match conditions: 48 49 ``` 50 Requested Element 51 Profile Profile 52 --------- -------- 53 None None Always matches 54 'string' None Always matches 55 None 'string' Does not match. Can't generate multiple APIs 56 or profiles, so if an API/profile constraint 57 is present, it must be asked for explicitly. 58 'string' 'string' Strings must match 59 ``` 60 61 ** In the future, we will allow regexes for the attributes, 62 not just strings, so that `api="^(gl|gles2)"` will match. Even 63 this isn't really quite enough, we might prefer something 64 like `"gl(core)|gles1(common-lite)"`.""" 65 # Match 'api', if present 66 elem_api = elem.get('api') 67 if elem_api: 68 if api is None: 69 raise UserWarning("No API requested, but 'api' attribute is present with value '" 70 + elem_api + "'") 71 elif api != elem_api: 72 # Requested API doesn't match attribute 73 return False 74 elem_profile = elem.get('profile') 75 if elem_profile: 76 if profile is None: 77 raise UserWarning("No profile requested, but 'profile' attribute is present with value '" 78 + elem_profile + "'") 79 elif profile != elem_profile: 80 # Requested profile doesn't match attribute 81 return False 82 return True 83 84 85class BaseInfo: 86 """Base class for information about a registry feature 87 (type/group/enum/command/API/extension). 88 89 Represents the state of a registry feature, used during API generation. 90 """ 91 92 def __init__(self, elem): 93 self.required = False 94 """should this feature be defined during header generation 95 (has it been removed by a profile or version)?""" 96 97 self.declared = False 98 "has this feature been defined already?" 99 100 self.elem = elem 101 "etree Element for this feature" 102 103 def resetState(self): 104 """Reset required/declared to initial values. Used 105 prior to generating a new API interface.""" 106 self.required = False 107 self.declared = False 108 109 def compareKeys(self, info, key, required = False): 110 """Return True if self.elem and info.elem have the same attribute 111 value for key. 112 If 'required' is not True, also returns True if neither element 113 has an attribute value for key.""" 114 115 if required and key not in self.elem.keys(): 116 return False 117 return self.elem.get(key) == info.elem.get(key) 118 119 def compareElem(self, info, infoName): 120 """Return True if self.elem and info.elem have the same definition. 121 info - the other object 122 infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 123 'extension'""" 124 125 if infoName == 'enum': 126 if self.compareKeys(info, 'extends'): 127 # Either both extend the same type, or no type 128 if (self.compareKeys(info, 'value', required = True) or 129 self.compareKeys(info, 'bitpos', required = True)): 130 # If both specify the same value or bit position, 131 # they're equal 132 return True 133 elif (self.compareKeys(info, 'extnumber') and 134 self.compareKeys(info, 'offset') and 135 self.compareKeys(info, 'dir')): 136 # If both specify the same relative offset, they're equal 137 return True 138 elif (self.compareKeys(info, 'alias')): 139 # If both are aliases of the same value 140 return True 141 else: 142 return False 143 else: 144 # The same enum can't extend two different types 145 return False 146 else: 147 # Non-<enum>s should never be redefined 148 return False 149 150 151class TypeInfo(BaseInfo): 152 """Registry information about a type. No additional state 153 beyond BaseInfo is required.""" 154 155 def __init__(self, elem): 156 BaseInfo.__init__(self, elem) 157 self.additionalValidity = [] 158 self.removedValidity = [] 159 160 def getMembers(self): 161 """Get a collection of all member elements for this type, if any.""" 162 return self.elem.findall('member') 163 164 def resetState(self): 165 BaseInfo.resetState(self) 166 self.additionalValidity = [] 167 self.removedValidity = [] 168 169 170class GroupInfo(BaseInfo): 171 """Registry information about a group of related enums 172 in an <enums> block, generally corresponding to a C "enum" type.""" 173 174 def __init__(self, elem): 175 BaseInfo.__init__(self, elem) 176 177 178class EnumInfo(BaseInfo): 179 """Registry information about an enum""" 180 181 def __init__(self, elem): 182 BaseInfo.__init__(self, elem) 183 self.type = elem.get('type') 184 """numeric type of the value of the <enum> tag 185 ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 )""" 186 if self.type is None: 187 self.type = '' 188 189 190class CmdInfo(BaseInfo): 191 """Registry information about a command""" 192 193 def __init__(self, elem): 194 BaseInfo.__init__(self, elem) 195 self.additionalValidity = [] 196 self.removedValidity = [] 197 198 def getParams(self): 199 """Get a collection of all param elements for this command, if any.""" 200 return self.elem.findall('param') 201 202 def resetState(self): 203 BaseInfo.resetState(self) 204 self.additionalValidity = [] 205 self.removedValidity = [] 206 207 208class FeatureInfo(BaseInfo): 209 """Registry information about an API <feature> 210 or <extension>.""" 211 212 def __init__(self, elem): 213 BaseInfo.__init__(self, elem) 214 self.name = elem.get('name') 215 "feature name string (e.g. 'VK_KHR_surface')" 216 217 self.emit = False 218 "has this feature been defined already?" 219 220 self.sortorder = int(elem.get('sortorder', 0)) 221 """explicit numeric sort key within feature and extension groups. 222 Defaults to 0.""" 223 224 # Determine element category (vendor). Only works 225 # for <extension> elements. 226 if elem.tag == 'feature': 227 # Element category (vendor) is meaningless for <feature> 228 self.category = 'VERSION' 229 """category, e.g. VERSION or khr/vendor tag""" 230 231 self.version = elem.get('name') 232 """feature name string""" 233 234 self.versionNumber = elem.get('number') 235 """versionNumber - API version number, taken from the 'number' 236 attribute of <feature>. Extensions do not have API version 237 numbers and are assigned number 0.""" 238 239 self.number = "0" 240 self.supported = None 241 else: 242 # Extract vendor portion of <APIprefix>_<vendor>_<name> 243 self.category = self.name.split('_', 2)[1] 244 self.version = "0" 245 self.versionNumber = "0" 246 self.number = elem.get('number') 247 """extension number, used for ordering and for assigning 248 enumerant offsets. <feature> features do not have extension 249 numbers and are assigned number 0.""" 250 251 # If there's no 'number' attribute, use 0, so sorting works 252 if self.number is None: 253 self.number = 0 254 self.supported = elem.get('supported') 255 256class SpirvInfo(BaseInfo): 257 """Registry information about an API <spirvextensions> 258 or <spirvcapability>.""" 259 260 def __init__(self, elem): 261 BaseInfo.__init__(self, elem) 262 263class Registry: 264 """Object representing an API registry, loaded from an XML file.""" 265 266 def __init__(self, gen=None, genOpts=None): 267 if gen is None: 268 # If not specified, give a default object so messaging will work 269 self.gen = OutputGenerator() 270 else: 271 self.gen = gen 272 "Output generator used to write headers / messages" 273 274 if genOpts is None: 275 self.genOpts = GeneratorOptions() 276 else: 277 self.genOpts = genOpts 278 "Options controlling features to write and how to format them" 279 280 self.gen.registry = self 281 self.gen.genOpts = self.genOpts 282 self.gen.genOpts.registry = self 283 284 self.tree = None 285 "ElementTree containing the root `<registry>`" 286 287 self.typedict = {} 288 "dictionary of TypeInfo objects keyed by type name" 289 290 self.groupdict = {} 291 "dictionary of GroupInfo objects keyed by group name" 292 293 self.enumdict = {} 294 "dictionary of EnumInfo objects keyed by enum name" 295 296 self.cmddict = {} 297 "dictionary of CmdInfo objects keyed by command name" 298 299 self.apidict = {} 300 "dictionary of FeatureInfo objects for `<feature>` elements keyed by API name" 301 302 self.extensions = [] 303 "list of `<extension>` Elements" 304 305 self.extdict = {} 306 "dictionary of FeatureInfo objects for `<extension>` elements keyed by extension name" 307 308 self.spirvextdict = {} 309 "dictionary of FeatureInfo objects for `<spirvextension>` elements keyed by spirv extension name" 310 311 self.spirvcapdict = {} 312 "dictionary of FeatureInfo objects for `<spirvcapability>` elements keyed by spirv capability name" 313 314 self.emitFeatures = False 315 """True to actually emit features for a version / extension, 316 or False to just treat them as emitted""" 317 318 self.breakPat = None 319 "regexp pattern to break on when generating names" 320 # self.breakPat = re.compile('VkFenceImportFlagBits.*') 321 322 self.requiredextensions = [] # Hack - can remove it after validity generator goes away 323 324 # ** Global types for automatic source generation ** 325 # Length Member data 326 self.commandextensiontuple = namedtuple('commandextensiontuple', 327 ['command', # The name of the command being modified 328 'value', # The value to append to the command 329 'extension']) # The name of the extension that added it 330 self.validextensionstructs = defaultdict(list) 331 self.commandextensionsuccesses = [] 332 self.commandextensionerrors = [] 333 334 self.filename = None 335 336 def loadElementTree(self, tree): 337 """Load ElementTree into a Registry object and parse it.""" 338 self.tree = tree 339 self.parseTree() 340 341 def loadFile(self, file): 342 """Load an API registry XML file into a Registry object and parse it""" 343 self.filename = file 344 self.tree = etree.parse(file) 345 self.parseTree() 346 347 def setGenerator(self, gen): 348 """Specify output generator object. 349 350 `None` restores the default generator.""" 351 self.gen = gen 352 self.gen.setRegistry(self) 353 354 def addElementInfo(self, elem, info, infoName, dictionary): 355 """Add information about an element to the corresponding dictionary. 356 357 Intended for internal use only. 358 359 - elem - `<type>`/`<enums>`/`<enum>`/`<command>`/`<feature>`/`<extension>`/`<spirvextension>`/`<spirvcapability>` Element 360 - info - corresponding {Type|Group|Enum|Cmd|Feature|Spirv}Info object 361 - infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' / 'spirvextension' / 'spirvcapability' 362 - dictionary - self.{type|group|enum|cmd|api|ext|spirvext|spirvcap}dict 363 364 If the Element has an 'api' attribute, the dictionary key is the 365 tuple (name,api). If not, the key is the name. 'name' is an 366 attribute of the Element""" 367 # self.gen.logMsg('diag', 'Adding ElementInfo.required =', 368 # info.required, 'name =', elem.get('name')) 369 api = elem.get('api') 370 if api: 371 key = (elem.get('name'), api) 372 else: 373 key = elem.get('name') 374 if key in dictionary: 375 if not dictionary[key].compareElem(info, infoName): 376 self.gen.logMsg('warn', 'Attempt to redefine', key, 377 '(this should not happen)') 378 else: 379 True 380 else: 381 dictionary[key] = info 382 383 def lookupElementInfo(self, fname, dictionary): 384 """Find a {Type|Enum|Cmd}Info object by name. 385 386 Intended for internal use only. 387 388 If an object qualified by API name exists, use that. 389 390 - fname - name of type / enum / command 391 - dictionary - self.{type|enum|cmd}dict""" 392 key = (fname, self.genOpts.apiname) 393 if key in dictionary: 394 # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) 395 return dictionary[key] 396 if fname in dictionary: 397 # self.gen.logMsg('diag', 'Found generic element for feature', fname) 398 return dictionary[fname] 399 400 return None 401 402 def breakOnName(self, regexp): 403 """Specify a feature name regexp to break on when generating features.""" 404 self.breakPat = re.compile(regexp) 405 406 def parseTree(self): 407 """Parse the registry Element, once created""" 408 # This must be the Element for the root <registry> 409 self.reg = self.tree.getroot() 410 411 # Create dictionary of registry types from toplevel <types> tags 412 # and add 'name' attribute to each <type> tag (where missing) 413 # based on its <name> element. 414 # 415 # There's usually one <types> block; more are OK 416 # Required <type> attributes: 'name' or nested <name> tag contents 417 self.typedict = {} 418 for type_elem in self.reg.findall('types/type'): 419 # If the <type> doesn't already have a 'name' attribute, set 420 # it from contents of its <name> tag. 421 if type_elem.get('name') is None: 422 type_elem.set('name', type_elem.find('name').text) 423 self.addElementInfo(type_elem, TypeInfo(type_elem), 'type', self.typedict) 424 425 # Create dictionary of registry enum groups from <enums> tags. 426 # 427 # Required <enums> attributes: 'name'. If no name is given, one is 428 # generated, but that group can't be identified and turned into an 429 # enum type definition - it's just a container for <enum> tags. 430 self.groupdict = {} 431 for group in self.reg.findall('enums'): 432 self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) 433 434 # Create dictionary of registry enums from <enum> tags 435 # 436 # <enums> tags usually define different namespaces for the values 437 # defined in those tags, but the actual names all share the 438 # same dictionary. 439 # Required <enum> attributes: 'name', 'value' 440 # For containing <enums> which have type="enum" or type="bitmask", 441 # tag all contained <enum>s are required. This is a stopgap until 442 # a better scheme for tagging core and extension enums is created. 443 self.enumdict = {} 444 for enums in self.reg.findall('enums'): 445 required = (enums.get('type') is not None) 446 for enum in enums.findall('enum'): 447 enumInfo = EnumInfo(enum) 448 enumInfo.required = required 449 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 450 451 # Create dictionary of registry commands from <command> tags 452 # and add 'name' attribute to each <command> tag (where missing) 453 # based on its <proto><name> element. 454 # 455 # There's usually only one <commands> block; more are OK. 456 # Required <command> attributes: 'name' or <proto><name> tag contents 457 self.cmddict = {} 458 # List of commands which alias others. Contains 459 # [ aliasName, element ] 460 # for each alias 461 cmdAlias = [] 462 for cmd in self.reg.findall('commands/command'): 463 # If the <command> doesn't already have a 'name' attribute, set 464 # it from contents of its <proto><name> tag. 465 name = cmd.get('name') 466 if name is None: 467 name = cmd.set('name', cmd.find('proto/name').text) 468 ci = CmdInfo(cmd) 469 self.addElementInfo(cmd, ci, 'command', self.cmddict) 470 alias = cmd.get('alias') 471 if alias: 472 cmdAlias.append([name, alias, cmd]) 473 474 # Now loop over aliases, injecting a copy of the aliased command's 475 # Element with the aliased prototype name replaced with the command 476 # name - if it exists. 477 for (name, alias, cmd) in cmdAlias: 478 if alias in self.cmddict: 479 aliasInfo = self.cmddict[alias] 480 cmdElem = copy.deepcopy(aliasInfo.elem) 481 cmdElem.find('proto/name').text = name 482 cmdElem.set('name', name) 483 cmdElem.set('alias', alias) 484 ci = CmdInfo(cmdElem) 485 # Replace the dictionary entry for the CmdInfo element 486 self.cmddict[name] = ci 487 488 # @ newString = etree.tostring(base, encoding="unicode").replace(aliasValue, aliasName) 489 # @elem.append(etree.fromstring(replacement)) 490 else: 491 self.gen.logMsg('warn', 'No matching <command> found for command', 492 cmd.get('name'), 'alias', alias) 493 494 # Create dictionaries of API and extension interfaces 495 # from toplevel <api> and <extension> tags. 496 self.apidict = {} 497 for feature in self.reg.findall('feature'): 498 featureInfo = FeatureInfo(feature) 499 self.addElementInfo(feature, featureInfo, 'feature', self.apidict) 500 501 # Add additional enums defined only in <feature> tags 502 # to the corresponding enumerated type. 503 # When seen here, the <enum> element, processed to contain the 504 # numeric enum value, is added to the corresponding <enums> 505 # element, as well as adding to the enum dictionary. It is no 506 # longer removed from the <require> element it is introduced in. 507 # Instead, generateRequiredInterface ignores <enum> elements 508 # that extend enumerated types. 509 # 510 # For <enum> tags which are actually just constants, if there's 511 # no 'extends' tag but there is a 'value' or 'bitpos' tag, just 512 # add an EnumInfo record to the dictionary. That works because 513 # output generation of constants is purely dependency-based, and 514 # doesn't need to iterate through the XML tags. 515 for elem in feature.findall('require'): 516 for enum in elem.findall('enum'): 517 addEnumInfo = False 518 groupName = enum.get('extends') 519 if groupName is not None: 520 # self.gen.logMsg('diag', 'Found extension enum', 521 # enum.get('name')) 522 # Add version number attribute to the <enum> element 523 enum.set('version', featureInfo.version) 524 # Look up the GroupInfo with matching groupName 525 if groupName in self.groupdict: 526 # self.gen.logMsg('diag', 'Matching group', 527 # groupName, 'found, adding element...') 528 gi = self.groupdict[groupName] 529 gi.elem.append(copy.deepcopy(enum)) 530 else: 531 self.gen.logMsg('warn', 'NO matching group', 532 groupName, 'for enum', enum.get('name'), 'found.') 533 addEnumInfo = True 534 elif enum.get('value') or enum.get('bitpos') or enum.get('alias'): 535 # self.gen.logMsg('diag', 'Adding extension constant "enum"', 536 # enum.get('name')) 537 addEnumInfo = True 538 if addEnumInfo: 539 enumInfo = EnumInfo(enum) 540 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 541 542 self.extensions = self.reg.findall('extensions/extension') 543 self.extdict = {} 544 for feature in self.extensions: 545 featureInfo = FeatureInfo(feature) 546 self.addElementInfo(feature, featureInfo, 'extension', self.extdict) 547 548 # Add additional enums defined only in <extension> tags 549 # to the corresponding core type. 550 # Algorithm matches that of enums in a "feature" tag as above. 551 # 552 # This code also adds a 'extnumber' attribute containing the 553 # extension number, used for enumerant value calculation. 554 for elem in feature.findall('require'): 555 for enum in elem.findall('enum'): 556 addEnumInfo = False 557 groupName = enum.get('extends') 558 if groupName is not None: 559 # self.gen.logMsg('diag', 'Found extension enum', 560 # enum.get('name')) 561 562 # Add <extension> block's extension number attribute to 563 # the <enum> element unless specified explicitly, such 564 # as when redefining an enum in another extension. 565 extnumber = enum.get('extnumber') 566 if not extnumber: 567 enum.set('extnumber', featureInfo.number) 568 569 enum.set('extname', featureInfo.name) 570 enum.set('supported', featureInfo.supported) 571 # Look up the GroupInfo with matching groupName 572 if groupName in self.groupdict: 573 # self.gen.logMsg('diag', 'Matching group', 574 # groupName, 'found, adding element...') 575 gi = self.groupdict[groupName] 576 gi.elem.append(copy.deepcopy(enum)) 577 else: 578 self.gen.logMsg('warn', 'NO matching group', 579 groupName, 'for enum', enum.get('name'), 'found.') 580 addEnumInfo = True 581 elif enum.get('value') or enum.get('bitpos') or enum.get('alias'): 582 # self.gen.logMsg('diag', 'Adding extension constant "enum"', 583 # enum.get('name')) 584 addEnumInfo = True 585 if addEnumInfo: 586 enumInfo = EnumInfo(enum) 587 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 588 589 # Construct a "validextensionstructs" list for parent structures 590 # based on "structextends" tags in child structures 591 disabled_types = [] 592 for disabled_ext in self.reg.findall('extensions/extension[@supported="disabled"]'): 593 for type_elem in disabled_ext.findall("*/type"): 594 disabled_types.append(type_elem.get('name')) 595 for type_elem in self.reg.findall('types/type'): 596 if type_elem.get('name') not in disabled_types: 597 parentStructs = type_elem.get('structextends') 598 if parentStructs is not None: 599 for parent in parentStructs.split(','): 600 # self.gen.logMsg('diag', type.get('name'), 'extends', parent) 601 self.validextensionstructs[parent].append(type_elem.get('name')) 602 # Sort the lists so they don't depend on the XML order 603 for parent in self.validextensionstructs: 604 self.validextensionstructs[parent].sort() 605 606 # Parse out all spirv tags in dictionaries 607 # Use addElementInfo to catch duplicates 608 for spirv in self.reg.findall('spirvextensions/spirvextension'): 609 spirvInfo = SpirvInfo(spirv) 610 self.addElementInfo(spirv, spirvInfo, 'spirvextension', self.spirvextdict) 611 for spirv in self.reg.findall('spirvcapabilities/spirvcapability'): 612 spirvInfo = SpirvInfo(spirv) 613 self.addElementInfo(spirv, spirvInfo, 'spirvcapability', self.spirvcapdict) 614 615 def dumpReg(self, maxlen=120, filehandle=sys.stdout): 616 """Dump all the dictionaries constructed from the Registry object. 617 618 Diagnostic to dump the dictionaries to specified file handle (default stdout). 619 Truncates type / enum / command elements to maxlen characters (default 120)""" 620 write('***************************************', file=filehandle) 621 write(' ** Dumping Registry contents **', file=filehandle) 622 write('***************************************', file=filehandle) 623 write('// Types', file=filehandle) 624 for name in self.typedict: 625 tobj = self.typedict[name] 626 write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) 627 write('// Groups', file=filehandle) 628 for name in self.groupdict: 629 gobj = self.groupdict[name] 630 write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) 631 write('// Enums', file=filehandle) 632 for name in self.enumdict: 633 eobj = self.enumdict[name] 634 write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) 635 write('// Commands', file=filehandle) 636 for name in self.cmddict: 637 cobj = self.cmddict[name] 638 write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) 639 write('// APIs', file=filehandle) 640 for key in self.apidict: 641 write(' API Version ', key, '->', 642 etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) 643 write('// Extensions', file=filehandle) 644 for key in self.extdict: 645 write(' Extension', key, '->', 646 etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) 647 write('// SPIR-V', file=filehandle) 648 for key in self.spirvextdict: 649 write(' SPIR-V Extension', key, '->', 650 etree.tostring(self.spirvextdict[key].elem)[0:maxlen], file=filehandle) 651 for key in self.spirvcapdict: 652 write(' SPIR-V Capability', key, '->', 653 etree.tostring(self.spirvcapdict[key].elem)[0:maxlen], file=filehandle) 654 655 def markTypeRequired(self, typename, required): 656 """Require (along with its dependencies) or remove (but not its dependencies) a type. 657 658 - typename - name of type 659 - required - boolean (to tag features as required or not) 660 """ 661 self.gen.logMsg('diag', 'tagging type:', typename, '-> required =', required) 662 # Get TypeInfo object for <type> tag corresponding to typename 663 typeinfo = self.lookupElementInfo(typename, self.typedict) 664 if typeinfo is not None: 665 if required: 666 # Tag type dependencies in 'alias' and 'required' attributes as 667 # required. This does not un-tag dependencies in a <remove> 668 # tag. See comments in markRequired() below for the reason. 669 for attrib_name in ['requires', 'alias']: 670 depname = typeinfo.elem.get(attrib_name) 671 if depname: 672 self.gen.logMsg('diag', 'Generating dependent type', 673 depname, 'for', attrib_name, 'type', typename) 674 # Don't recurse on self-referential structures. 675 if typename != depname: 676 self.markTypeRequired(depname, required) 677 else: 678 self.gen.logMsg('diag', 'type', typename, 'is self-referential') 679 # Tag types used in defining this type (e.g. in nested 680 # <type> tags) 681 # Look for <type> in entire <command> tree, 682 # not just immediate children 683 for subtype in typeinfo.elem.findall('.//type'): 684 self.gen.logMsg('diag', 'markRequired: type requires dependent <type>', subtype.text) 685 if typename != subtype.text: 686 self.markTypeRequired(subtype.text, required) 687 else: 688 self.gen.logMsg('diag', 'type', typename, 'is self-referential') 689 # Tag enums used in defining this type, for example in 690 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 691 for subenum in typeinfo.elem.findall('.//enum'): 692 self.gen.logMsg('diag', 'markRequired: type requires dependent <enum>', subenum.text) 693 self.markEnumRequired(subenum.text, required) 694 # Tag type dependency in 'bitvalues' attributes as 695 # required. This ensures that the bit values for a flag 696 # are emitted 697 depType = typeinfo.elem.get('bitvalues') 698 if depType: 699 self.gen.logMsg('diag', 'Generating bitflag type', 700 depType, 'for type', typename) 701 self.markTypeRequired(depType, required) 702 group = self.lookupElementInfo(depType, self.groupdict) 703 if group is not None: 704 group.flagType = typeinfo 705 706 typeinfo.required = required 707 elif '.h' not in typename: 708 self.gen.logMsg('warn', 'type:', typename, 'IS NOT DEFINED') 709 710 def markEnumRequired(self, enumname, required): 711 """Mark an enum as required or not. 712 713 - enumname - name of enum 714 - required - boolean (to tag features as required or not)""" 715 716 self.gen.logMsg('diag', 'tagging enum:', enumname, '-> required =', required) 717 enum = self.lookupElementInfo(enumname, self.enumdict) 718 if enum is not None: 719 # If the enum is part of a group, and is being removed, then 720 # look it up in that <group> tag and remove it there, so that it 721 # isn't visible to generators (which traverse the <group> tag 722 # elements themselves). 723 # This isn't the most robust way of doing this, since a removed 724 # enum that's later required again will no longer have a group 725 # element, but it makes the change non-intrusive on generator 726 # code. 727 if required is False: 728 groupName = enum.elem.get('extends') 729 if groupName is not None: 730 # Look up the Info with matching groupName 731 if groupName in self.groupdict: 732 gi = self.groupdict[groupName] 733 gienum = gi.elem.find("enum[@name='" + enumname + "']") 734 if gienum is not None: 735 # Remove copy of this enum from the group 736 gi.elem.remove(gienum) 737 else: 738 self.gen.logMsg('warn', 'Cannot remove enum', 739 enumname, 'not found in group', 740 groupName) 741 else: 742 self.gen.logMsg('warn', 'Cannot remove enum', 743 enumname, 'from nonexistent group', 744 groupName) 745 746 enum.required = required 747 # Tag enum dependencies in 'alias' attribute as required 748 depname = enum.elem.get('alias') 749 if depname: 750 self.gen.logMsg('diag', 'Generating dependent enum', 751 depname, 'for alias', enumname, 'required =', enum.required) 752 self.markEnumRequired(depname, required) 753 else: 754 self.gen.logMsg('warn', 'enum:', enumname, 'IS NOT DEFINED') 755 756 def markCmdRequired(self, cmdname, required): 757 """Mark a command as required or not. 758 759 - cmdname - name of command 760 - required - boolean (to tag features as required or not)""" 761 self.gen.logMsg('diag', 'tagging command:', cmdname, '-> required =', required) 762 cmd = self.lookupElementInfo(cmdname, self.cmddict) 763 if cmd is not None: 764 cmd.required = required 765 766 # Tag command dependencies in 'alias' attribute as required 767 # 768 # This is usually not done, because command 'aliases' are not 769 # actual C language aliases like type and enum aliases. Instead 770 # they are just duplicates of the function signature of the 771 # alias. This means that there is no dependency of a command 772 # alias on what it aliases. One exception is validity includes, 773 # where the spec markup needs the promoted-to validity include 774 # even if only the promoted-from command is being built. 775 if self.genOpts.requireCommandAliases: 776 depname = cmd.elem.get('alias') 777 if depname: 778 self.gen.logMsg('diag', 'Generating dependent command', 779 depname, 'for alias', cmdname) 780 self.markCmdRequired(depname, required) 781 782 # Tag all parameter types of this command as required. 783 # This DOES NOT remove types of commands in a <remove> 784 # tag, because many other commands may use the same type. 785 # We could be more clever and reference count types, 786 # instead of using a boolean. 787 if required: 788 # Look for <type> in entire <command> tree, 789 # not just immediate children 790 for type_elem in cmd.elem.findall('.//type'): 791 self.gen.logMsg('diag', 'markRequired: command implicitly requires dependent type', type_elem.text) 792 self.markTypeRequired(type_elem.text, required) 793 else: 794 self.gen.logMsg('warn', 'command:', cmdname, 'IS NOT DEFINED') 795 796 def markRequired(self, featurename, feature, required): 797 """Require or remove features specified in the Element. 798 799 - featurename - name of the feature 800 - feature - Element for `<require>` or `<remove>` tag 801 - required - boolean (to tag features as required or not)""" 802 self.gen.logMsg('diag', 'markRequired (feature = <too long to print>, required =', required, ')') 803 804 # Loop over types, enums, and commands in the tag 805 # @@ It would be possible to respect 'api' and 'profile' attributes 806 # in individual features, but that's not done yet. 807 for typeElem in feature.findall('type'): 808 self.markTypeRequired(typeElem.get('name'), required) 809 for enumElem in feature.findall('enum'): 810 self.markEnumRequired(enumElem.get('name'), required) 811 for cmdElem in feature.findall('command'): 812 self.markCmdRequired(cmdElem.get('name'), required) 813 814 # Extensions may need to extend existing commands or other items in the future. 815 # So, look for extend tags. 816 for extendElem in feature.findall('extend'): 817 extendType = extendElem.get('type') 818 if extendType == 'command': 819 commandName = extendElem.get('name') 820 successExtends = extendElem.get('successcodes') 821 if successExtends is not None: 822 for success in successExtends.split(','): 823 self.commandextensionsuccesses.append(self.commandextensiontuple(command=commandName, 824 value=success, 825 extension=featurename)) 826 errorExtends = extendElem.get('errorcodes') 827 if errorExtends is not None: 828 for error in errorExtends.split(','): 829 self.commandextensionerrors.append(self.commandextensiontuple(command=commandName, 830 value=error, 831 extension=featurename)) 832 else: 833 self.gen.logMsg('warn', 'extend type:', extendType, 'IS NOT SUPPORTED') 834 835 def getAlias(self, elem, dict): 836 """Check for an alias in the same require block. 837 838 - elem - Element to check for an alias""" 839 840 # Try to find an alias 841 alias = elem.get('alias') 842 if alias is None: 843 name = elem.get('name') 844 typeinfo = self.lookupElementInfo(name, dict) 845 alias = typeinfo.elem.get('alias') 846 847 return alias 848 849 def checkForCorrectionAliases(self, alias, require, tag): 850 """Check for an alias in the same require block. 851 852 - alias - String name of the alias 853 - require - `<require>` block from the registry 854 - tag - tag to look for in the require block""" 855 856 # For the time being, the code below is bypassed. It has the effect 857 # of excluding "spelling aliases" created to comply with the style 858 # guide, but this leaves references out of the specification and 859 # causes broken internal links. 860 # 861 # if alias and require.findall(tag + "[@name='" + alias + "']"): 862 # return True 863 864 return False 865 866 def fillFeatureDictionary(self, interface, featurename, api, profile): 867 """Capture added interfaces for a `<version>` or `<extension>`. 868 869 - interface - Element for `<version>` or `<extension>`, containing 870 `<require>` and `<remove>` tags 871 - featurename - name of the feature 872 - api - string specifying API name being generated 873 - profile - string specifying API profile being generated""" 874 875 # Explicitly initialize known types - errors for unhandled categories 876 self.gen.featureDictionary[featurename] = { 877 "enumconstant": {}, 878 "command": {}, 879 "enum": {}, 880 "struct": {}, 881 "handle": {}, 882 "basetype": {}, 883 "include": {}, 884 "define": {}, 885 "bitmask": {}, 886 "union": {}, 887 "funcpointer": {}, 888 } 889 890 # <require> marks things that are required by this version/profile 891 for require in interface.findall('require'): 892 if matchAPIProfile(api, profile, require): 893 894 # Determine the required extension or version needed for a require block 895 # Assumes that only one of these is specified 896 required_key = require.get('feature') 897 if required_key is None: 898 required_key = require.get('extension') 899 900 # Loop over types, enums, and commands in the tag 901 for typeElem in require.findall('type'): 902 typename = typeElem.get('name') 903 typeinfo = self.lookupElementInfo(typename, self.typedict) 904 905 if typeinfo: 906 # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible. 907 alias = self.getAlias(typeElem, self.typedict) 908 if not self.checkForCorrectionAliases(alias, require, 'type'): 909 # Resolve the type info to the actual type, so we get an accurate read for 'structextends' 910 while alias: 911 typeinfo = self.lookupElementInfo(alias, self.typedict) 912 alias = typeinfo.elem.get('alias') 913 914 typecat = typeinfo.elem.get('category') 915 typeextends = typeinfo.elem.get('structextends') 916 if not required_key in self.gen.featureDictionary[featurename][typecat]: 917 self.gen.featureDictionary[featurename][typecat][required_key] = {} 918 if not typeextends in self.gen.featureDictionary[featurename][typecat][required_key]: 919 self.gen.featureDictionary[featurename][typecat][required_key][typeextends] = [] 920 self.gen.featureDictionary[featurename][typecat][required_key][typeextends].append(typename) 921 else: 922 self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) 923 924 925 for enumElem in require.findall('enum'): 926 enumname = enumElem.get('name') 927 typeinfo = self.lookupElementInfo(enumname, self.enumdict) 928 929 # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible. 930 alias = self.getAlias(enumElem, self.enumdict) 931 if not self.checkForCorrectionAliases(alias, require, 'enum'): 932 enumextends = enumElem.get('extends') 933 if not required_key in self.gen.featureDictionary[featurename]['enumconstant']: 934 self.gen.featureDictionary[featurename]['enumconstant'][required_key] = {} 935 if not enumextends in self.gen.featureDictionary[featurename]['enumconstant'][required_key]: 936 self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends] = [] 937 self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends].append(enumname) 938 else: 939 self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) 940 941 for cmdElem in require.findall('command'): 942 # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible. 943 alias = self.getAlias(cmdElem, self.cmddict) 944 if not self.checkForCorrectionAliases(alias, require, 'command'): 945 if not required_key in self.gen.featureDictionary[featurename]['command']: 946 self.gen.featureDictionary[featurename]['command'][required_key] = [] 947 self.gen.featureDictionary[featurename]['command'][required_key].append(cmdElem.get('name')) 948 else: 949 self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) 950 951 def requireAndRemoveFeatures(self, interface, featurename, api, profile): 952 """Process `<require>` and `<remove>` tags for a `<version>` or `<extension>`. 953 954 - interface - Element for `<version>` or `<extension>`, containing 955 `<require>` and `<remove>` tags 956 - featurename - name of the feature 957 - api - string specifying API name being generated 958 - profile - string specifying API profile being generated""" 959 960 # <require> marks things that are required by this version/profile 961 for feature in interface.findall('require'): 962 if matchAPIProfile(api, profile, feature): 963 self.markRequired(featurename, feature, True) 964 965 # <remove> marks things that are removed by this version/profile 966 for feature in interface.findall('remove'): 967 if matchAPIProfile(api, profile, feature): 968 self.markRequired(featurename, feature, False) 969 970 def assignAdditionalValidity(self, interface, api, profile): 971 # Loop over all usage inside all <require> tags. 972 for feature in interface.findall('require'): 973 if matchAPIProfile(api, profile, feature): 974 for v in feature.findall('usage'): 975 if v.get('command'): 976 self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v)) 977 if v.get('struct'): 978 self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v)) 979 980 # Loop over all usage inside all <remove> tags. 981 for feature in interface.findall('remove'): 982 if matchAPIProfile(api, profile, feature): 983 for v in feature.findall('usage'): 984 if v.get('command'): 985 self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v)) 986 if v.get('struct'): 987 self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v)) 988 989 def generateFeature(self, fname, ftype, dictionary): 990 """Generate a single type / enum group / enum / command, 991 and all its dependencies as needed. 992 993 - fname - name of feature (`<type>`/`<enum>`/`<command>`) 994 - ftype - type of feature, 'type' | 'enum' | 'command' 995 - dictionary - of *Info objects - self.{type|enum|cmd}dict""" 996 997 self.gen.logMsg('diag', 'generateFeature: generating', ftype, fname) 998 f = self.lookupElementInfo(fname, dictionary) 999 if f is None: 1000 # No such feature. This is an error, but reported earlier 1001 self.gen.logMsg('diag', 'No entry found for feature', fname, 1002 'returning!') 1003 return 1004 1005 # If feature isn't required, or has already been declared, return 1006 if not f.required: 1007 self.gen.logMsg('diag', 'Skipping', ftype, fname, '(not required)') 1008 return 1009 if f.declared: 1010 self.gen.logMsg('diag', 'Skipping', ftype, fname, '(already declared)') 1011 return 1012 # Always mark feature declared, as though actually emitted 1013 f.declared = True 1014 1015 # Determine if this is an alias, and of what, if so 1016 alias = f.elem.get('alias') 1017 if alias: 1018 self.gen.logMsg('diag', fname, 'is an alias of', alias) 1019 1020 # Pull in dependent declaration(s) of the feature. 1021 # For types, there may be one type in the 'requires' attribute of 1022 # the element, one in the 'alias' attribute, and many in 1023 # embedded <type> and <enum> tags within the element. 1024 # For commands, there may be many in <type> tags within the element. 1025 # For enums, no dependencies are allowed (though perhaps if you 1026 # have a uint64 enum, it should require that type). 1027 genProc = None 1028 followupFeature = None 1029 if ftype == 'type': 1030 genProc = self.gen.genType 1031 1032 # Generate type dependencies in 'alias' and 'requires' attributes 1033 if alias: 1034 self.generateFeature(alias, 'type', self.typedict) 1035 requires = f.elem.get('requires') 1036 if requires: 1037 self.gen.logMsg('diag', 'Generating required dependent type', 1038 requires) 1039 self.generateFeature(requires, 'type', self.typedict) 1040 1041 # Generate types used in defining this type (e.g. in nested 1042 # <type> tags) 1043 # Look for <type> in entire <command> tree, 1044 # not just immediate children 1045 for subtype in f.elem.findall('.//type'): 1046 self.gen.logMsg('diag', 'Generating required dependent <type>', 1047 subtype.text) 1048 self.generateFeature(subtype.text, 'type', self.typedict) 1049 1050 # Generate enums used in defining this type, for example in 1051 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 1052 for subtype in f.elem.findall('.//enum'): 1053 self.gen.logMsg('diag', 'Generating required dependent <enum>', 1054 subtype.text) 1055 self.generateFeature(subtype.text, 'enum', self.enumdict) 1056 1057 # If the type is an enum group, look up the corresponding 1058 # group in the group dictionary and generate that instead. 1059 if f.elem.get('category') == 'enum': 1060 self.gen.logMsg('diag', 'Type', fname, 'is an enum group, so generate that instead') 1061 group = self.lookupElementInfo(fname, self.groupdict) 1062 if alias is not None: 1063 # An alias of another group name. 1064 # Pass to genGroup with 'alias' parameter = aliased name 1065 self.gen.logMsg('diag', 'Generating alias', fname, 1066 'for enumerated type', alias) 1067 # Now, pass the *aliased* GroupInfo to the genGroup, but 1068 # with an additional parameter which is the alias name. 1069 genProc = self.gen.genGroup 1070 f = self.lookupElementInfo(alias, self.groupdict) 1071 elif group is None: 1072 self.gen.logMsg('warn', 'Skipping enum type', fname, 1073 ': No matching enumerant group') 1074 return 1075 else: 1076 genProc = self.gen.genGroup 1077 f = group 1078 1079 # @ The enum group is not ready for generation. At this 1080 # @ point, it contains all <enum> tags injected by 1081 # @ <extension> tags without any verification of whether 1082 # @ they're required or not. It may also contain 1083 # @ duplicates injected by multiple consistent 1084 # @ definitions of an <enum>. 1085 1086 # @ Pass over each enum, marking its enumdict[] entry as 1087 # @ required or not. Mark aliases of enums as required, 1088 # @ too. 1089 1090 enums = group.elem.findall('enum') 1091 1092 self.gen.logMsg('diag', 'generateFeature: checking enums for group', fname) 1093 1094 # Check for required enums, including aliases 1095 # LATER - Check for, report, and remove duplicates? 1096 enumAliases = [] 1097 for elem in enums: 1098 name = elem.get('name') 1099 1100 required = False 1101 1102 extname = elem.get('extname') 1103 version = elem.get('version') 1104 if extname is not None: 1105 # 'supported' attribute was injected when the <enum> element was 1106 # moved into the <enums> group in Registry.parseTree() 1107 if self.genOpts.defaultExtensions == elem.get('supported'): 1108 required = True 1109 elif re.match(self.genOpts.addExtensions, extname) is not None: 1110 required = True 1111 elif version is not None: 1112 required = re.match(self.genOpts.emitversions, version) is not None 1113 else: 1114 required = True 1115 1116 self.gen.logMsg('diag', '* required =', required, 'for', name) 1117 if required: 1118 # Mark this element as required (in the element, not the EnumInfo) 1119 elem.set('required', 'true') 1120 # If it's an alias, track that for later use 1121 enumAlias = elem.get('alias') 1122 if enumAlias: 1123 enumAliases.append(enumAlias) 1124 for elem in enums: 1125 name = elem.get('name') 1126 if name in enumAliases: 1127 elem.set('required', 'true') 1128 self.gen.logMsg('diag', '* also need to require alias', name) 1129 if f.elem.get('category') == 'bitmask': 1130 followupFeature = f.elem.get('bitvalues') 1131 elif ftype == 'command': 1132 # Generate command dependencies in 'alias' attribute 1133 if alias: 1134 self.generateFeature(alias, 'command', self.cmddict) 1135 1136 genProc = self.gen.genCmd 1137 for type_elem in f.elem.findall('.//type'): 1138 depname = type_elem.text 1139 self.gen.logMsg('diag', 'Generating required parameter type', 1140 depname) 1141 self.generateFeature(depname, 'type', self.typedict) 1142 elif ftype == 'enum': 1143 # Generate enum dependencies in 'alias' attribute 1144 if alias: 1145 self.generateFeature(alias, 'enum', self.enumdict) 1146 genProc = self.gen.genEnum 1147 1148 # Actually generate the type only if emitting declarations 1149 if self.emitFeatures: 1150 self.gen.logMsg('diag', 'Emitting', ftype, 'decl for', fname) 1151 genProc(f, fname, alias) 1152 else: 1153 self.gen.logMsg('diag', 'Skipping', ftype, fname, 1154 '(should not be emitted)') 1155 1156 if followupFeature: 1157 self.gen.logMsg('diag', 'Generating required bitvalues <enum>', 1158 followupFeature) 1159 self.generateFeature(followupFeature, "type", self.typedict) 1160 1161 def generateRequiredInterface(self, interface): 1162 """Generate all interfaces required by an API version or extension. 1163 1164 - interface - Element for `<version>` or `<extension>`""" 1165 1166 # Loop over all features inside all <require> tags. 1167 for features in interface.findall('require'): 1168 for t in features.findall('type'): 1169 self.generateFeature(t.get('name'), 'type', self.typedict) 1170 for e in features.findall('enum'): 1171 # If this is an enum extending an enumerated type, don't 1172 # generate it - this has already been done in reg.parseTree, 1173 # by copying this element into the enumerated type. 1174 enumextends = e.get('extends') 1175 if not enumextends: 1176 self.generateFeature(e.get('name'), 'enum', self.enumdict) 1177 for c in features.findall('command'): 1178 self.generateFeature(c.get('name'), 'command', self.cmddict) 1179 1180 def generateSpirv(self, spirv, dictionary): 1181 if spirv is None: 1182 self.gen.logMsg('diag', 'No entry found for element', name, 1183 'returning!') 1184 return 1185 1186 name = spirv.elem.get('name') 1187 # No known alias for spirv elements 1188 alias = None 1189 if spirv.emit: 1190 genProc = self.gen.genSpirv 1191 genProc(spirv, name, alias) 1192 1193 def stripUnsupportedAPIs(self, dictionary, attribute, supportedDictionary): 1194 """Strip unsupported APIs from attributes of APIs. 1195 dictionary - *Info dictionary of APIs to be updated 1196 attribute - attribute name to look for in each API 1197 supportedDictionary - dictionary in which to look for supported 1198 API elements in the attribute""" 1199 1200 for key in dictionary: 1201 eleminfo = dictionary[key] 1202 attribstring = eleminfo.elem.get(attribute) 1203 if attribstring is not None: 1204 apis = [] 1205 stripped = False 1206 for api in attribstring.split(','): 1207 ##print('Checking API {} referenced by {}'.format(api, key)) 1208 if supportedDictionary[api].required: 1209 apis.append(api) 1210 else: 1211 stripped = True 1212 ##print('\t**STRIPPING API {} from {}'.format(api, key)) 1213 1214 # Update the attribute after stripping stuff. 1215 # Could sort apis before joining, but it's not a clear win 1216 if stripped: 1217 eleminfo.elem.set(attribute, ','.join(apis)) 1218 1219 def apiGen(self): 1220 """Generate interface for specified versions using the current 1221 generator and generator options""" 1222 1223 self.gen.logMsg('diag', '*******************************************') 1224 self.gen.logMsg('diag', ' Registry.apiGen file:', self.genOpts.filename, 1225 'api:', self.genOpts.apiname, 1226 'profile:', self.genOpts.profile) 1227 self.gen.logMsg('diag', '*******************************************') 1228 1229 # Could reset required/declared flags for all features here. 1230 # This has been removed as never used. The initial motivation was 1231 # the idea of calling apiGen() repeatedly for different targets, but 1232 # this has never been done. The 20% or so build-time speedup that 1233 # might result is not worth the effort to make it actually work. 1234 # 1235 #@@ self.apiReset() 1236 1237 # Compile regexps used to select versions & extensions 1238 regVersions = re.compile(self.genOpts.versions) 1239 regEmitVersions = re.compile(self.genOpts.emitversions) 1240 regAddExtensions = re.compile(self.genOpts.addExtensions) 1241 regRemoveExtensions = re.compile(self.genOpts.removeExtensions) 1242 regEmitExtensions = re.compile(self.genOpts.emitExtensions) 1243 regEmitSpirv = re.compile(self.genOpts.emitSpirv) 1244 1245 # Get all matching API feature names & add to list of FeatureInfo 1246 # Note we used to select on feature version attributes, not names. 1247 features = [] 1248 apiMatch = False 1249 for key in self.apidict: 1250 fi = self.apidict[key] 1251 api = fi.elem.get('api') 1252 if apiNameMatch(self.genOpts.apiname, api): 1253 apiMatch = True 1254 if regVersions.match(fi.name): 1255 # Matches API & version #s being generated. Mark for 1256 # emission and add to the features[] list . 1257 # @@ Could use 'declared' instead of 'emit'? 1258 fi.emit = (regEmitVersions.match(fi.name) is not None) 1259 features.append(fi) 1260 if not fi.emit: 1261 self.gen.logMsg('diag', 'NOT tagging feature api =', api, 1262 'name =', fi.name, 'version =', fi.version, 1263 'for emission (does not match emitversions pattern)') 1264 else: 1265 self.gen.logMsg('diag', 'Including feature api =', api, 1266 'name =', fi.name, 'version =', fi.version, 1267 'for emission (matches emitversions pattern)') 1268 else: 1269 self.gen.logMsg('diag', 'NOT including feature api =', api, 1270 'name =', fi.name, 'version =', fi.version, 1271 '(does not match requested versions)') 1272 else: 1273 self.gen.logMsg('diag', 'NOT including feature api =', api, 1274 'name =', fi.name, 1275 '(does not match requested API)') 1276 if not apiMatch: 1277 self.gen.logMsg('warn', 'No matching API versions found!') 1278 1279 # Get all matching extensions, in order by their extension number, 1280 # and add to the list of features. 1281 # Start with extensions tagged with 'api' pattern matching the API 1282 # being generated. Add extensions matching the pattern specified in 1283 # regExtensions, then remove extensions matching the pattern 1284 # specified in regRemoveExtensions 1285 for (extName, ei) in sorted(self.extdict.items(), key=lambda x: x[1].number if x[1].number is not None else '0'): 1286 extName = ei.name 1287 include = False 1288 1289 # Include extension if defaultExtensions is not None and is 1290 # exactly matched by the 'supported' attribute. 1291 if apiNameMatch(self.genOpts.defaultExtensions, 1292 ei.elem.get('supported')): 1293 self.gen.logMsg('diag', 'Including extension', 1294 extName, "(defaultExtensions matches the 'supported' attribute)") 1295 include = True 1296 1297 # Include additional extensions if the extension name matches 1298 # the regexp specified in the generator options. This allows 1299 # forcing extensions into an interface even if they're not 1300 # tagged appropriately in the registry. 1301 # However we still respect the 'supported' attribute. 1302 if regAddExtensions.match(extName) is not None: 1303 if not apiNameMatch(self.genOpts.apiname, ei.elem.get('supported')): 1304 self.gen.logMsg('diag', 'NOT including extension', 1305 extName, '(matches explicitly requested, but does not match the \'supported\' attribute)') 1306 include = False 1307 else: 1308 self.gen.logMsg('diag', 'Including extension', 1309 extName, '(matches explicitly requested extensions to add)') 1310 include = True 1311 # Remove extensions if the name matches the regexp specified 1312 # in generator options. This allows forcing removal of 1313 # extensions from an interface even if they're tagged that 1314 # way in the registry. 1315 if regRemoveExtensions.match(extName) is not None: 1316 self.gen.logMsg('diag', 'Removing extension', 1317 extName, '(matches explicitly requested extensions to remove)') 1318 include = False 1319 1320 # If the extension is to be included, add it to the 1321 # extension features list. 1322 if include: 1323 ei.emit = (regEmitExtensions.match(extName) is not None) 1324 features.append(ei) 1325 if not ei.emit: 1326 self.gen.logMsg('diag', 'NOT tagging extension', 1327 extName, 1328 'for emission (does not match emitextensions pattern)') 1329 1330 # Hack - can be removed when validity generator goes away 1331 # (Jon) I'm not sure what this does, or if it should respect 1332 # the ei.emit flag above. 1333 self.requiredextensions.append(extName) 1334 else: 1335 self.gen.logMsg('diag', 'NOT including extension', 1336 extName, '(does not match api attribute or explicitly requested extensions)') 1337 1338 # Add all spirv elements to list 1339 # generators decide to emit them all or not 1340 # Currently no filtering as no client of these elements needs filtering 1341 spirvexts = [] 1342 for key in self.spirvextdict: 1343 si = self.spirvextdict[key] 1344 si.emit = (regEmitSpirv.match(key) is not None) 1345 spirvexts.append(si) 1346 spirvcaps = [] 1347 for key in self.spirvcapdict: 1348 si = self.spirvcapdict[key] 1349 si.emit = (regEmitSpirv.match(key) is not None) 1350 spirvcaps.append(si) 1351 1352 # Sort the features list, if a sort procedure is defined 1353 if self.genOpts.sortProcedure: 1354 self.genOpts.sortProcedure(features) 1355 # print('sortProcedure ->', [f.name for f in features]) 1356 1357 # Pass 1: loop over requested API versions and extensions tagging 1358 # types/commands/features as required (in an <require> block) or no 1359 # longer required (in an <remove> block). It is possible to remove 1360 # a feature in one version and restore it later by requiring it in 1361 # a later version. 1362 # If a profile other than 'None' is being generated, it must 1363 # match the profile attribute (if any) of the <require> and 1364 # <remove> tags. 1365 self.gen.logMsg('diag', 'PASS 1: TAG FEATURES') 1366 for f in features: 1367 self.gen.logMsg('diag', 'PASS 1: Tagging required and removed features for', 1368 f.name) 1369 self.fillFeatureDictionary(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) 1370 self.requireAndRemoveFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) 1371 self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile) 1372 1373 # Now, strip references to APIs that are not required. 1374 # At present such references may occur in: 1375 # Structs in <type category="struct"> 'structextends' attributes 1376 # Enums in <command> 'successcodes' and 'errorcodes' attributes 1377 self.stripUnsupportedAPIs(self.typedict, 'structextends', self.typedict) 1378 self.stripUnsupportedAPIs(self.cmddict, 'successcodes', self.enumdict) 1379 self.stripUnsupportedAPIs(self.cmddict, 'errorcodes', self.enumdict) 1380 1381 # @@May need to strip <spirvcapability> / <spirvextension> <enable> 1382 # tags of these forms: 1383 # <enable version="VK_API_VERSION_1_0"/> 1384 # <enable struct="VkPhysicalDeviceFeatures" feature="geometryShader" requires="VK_VERSION_1_0"/> 1385 # <enable extension="VK_KHR_shader_draw_parameters"/> 1386 # <enable property="VkPhysicalDeviceVulkan12Properties" member="shaderDenormPreserveFloat16" value="VK_TRUE" requires="VK_VERSION_1_2,VK_KHR_shader_float_controls"/> 1387 1388 # Pass 2: loop over specified API versions and extensions printing 1389 # declarations for required things which haven't already been 1390 # generated. 1391 self.gen.logMsg('diag', 'PASS 2: GENERATE INTERFACES FOR FEATURES') 1392 self.gen.beginFile(self.genOpts) 1393 for f in features: 1394 self.gen.logMsg('diag', 'PASS 2: Generating interface for', 1395 f.name) 1396 emit = self.emitFeatures = f.emit 1397 if not emit: 1398 self.gen.logMsg('diag', 'PASS 2: NOT declaring feature', 1399 f.elem.get('name'), 'because it is not tagged for emission') 1400 # Generate the interface (or just tag its elements as having been 1401 # emitted, if they haven't been). 1402 self.gen.beginFeature(f.elem, emit) 1403 self.generateRequiredInterface(f.elem) 1404 self.gen.endFeature() 1405 # Generate spirv elements 1406 for s in spirvexts: 1407 self.generateSpirv(s, self.spirvextdict) 1408 for s in spirvcaps: 1409 self.generateSpirv(s, self.spirvcapdict) 1410 self.gen.endFile() 1411 1412 def apiReset(self): 1413 """Reset type/enum/command dictionaries before generating another API. 1414 1415 Use between apiGen() calls to reset internal state.""" 1416 for datatype in self.typedict: 1417 self.typedict[datatype].resetState() 1418 for enum in self.enumdict: 1419 self.enumdict[enum].resetState() 1420 for cmd in self.cmddict: 1421 self.cmddict[cmd].resetState() 1422 for cmd in self.apidict: 1423 self.apidict[cmd].resetState() 1424 1425 def __validateStructLimittypes(self, struct): 1426 """Validate 'limittype' attributes for a single struct.""" 1427 limittypeDiags = namedtuple('limittypeDiags', ['missing', 'invalid']) 1428 badFields = defaultdict(lambda : limittypeDiags(missing=[], invalid=[])) 1429 validLimittypes = { 'min', 'max', 'bitmask', 'range', 'struct', 'noauto' } 1430 for member in struct.getMembers(): 1431 memberName = member.findtext('name') 1432 if memberName in ['sType', 'pNext']: 1433 continue 1434 limittype = member.get('limittype') 1435 if not limittype: 1436 badFields[struct.elem.get('name')].missing.append(memberName) 1437 elif limittype == 'struct': 1438 typeName = member.findtext('type') 1439 memberType = self.typedict[typeName] 1440 badFields.update(self.__validateStructLimittypes(memberType)) 1441 elif limittype not in validLimittypes: 1442 badFields[struct.elem.get('name')].invalid.append(memberName) 1443 return badFields 1444 1445 def __validateLimittype(self): 1446 """Validate 'limittype' attributes.""" 1447 self.gen.logMsg('diag', 'VALIDATING LIMITTYPE ATTRIBUTES') 1448 badFields = self.__validateStructLimittypes(self.typedict['VkPhysicalDeviceProperties2']) 1449 for featStructName in self.validextensionstructs['VkPhysicalDeviceProperties2']: 1450 featStruct = self.typedict[featStructName] 1451 badFields.update(self.__validateStructLimittypes(featStruct)) 1452 1453 if badFields: 1454 self.gen.logMsg('diag', 'SUMMARY OF FIELDS WITH INCORRECT LIMITTYPES') 1455 for key in sorted(badFields.keys()): 1456 diags = badFields[key] 1457 if diags.missing: 1458 self.gen.logMsg('diag', ' ', key, 'missing limittype:', ', '.join(badFields[key].missing)) 1459 if diags.invalid: 1460 self.gen.logMsg('diag', ' ', key, 'invalid limittype:', ', '.join(badFields[key].invalid)) 1461 return False 1462 return True 1463 1464 def validateRegistry(self): 1465 """Validate properties of the registry.""" 1466 return self.__validateLimittype() 1467