1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2020 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 # Tag command dependencies in 'alias' attribute as required 766 depname = cmd.elem.get('alias') 767 if depname: 768 self.gen.logMsg('diag', 'Generating dependent command', 769 depname, 'for alias', cmdname) 770 self.markCmdRequired(depname, required) 771 # Tag all parameter types of this command as required. 772 # This DOES NOT remove types of commands in a <remove> 773 # tag, because many other commands may use the same type. 774 # We could be more clever and reference count types, 775 # instead of using a boolean. 776 if required: 777 # Look for <type> in entire <command> tree, 778 # not just immediate children 779 for type_elem in cmd.elem.findall('.//type'): 780 self.gen.logMsg('diag', 'markRequired: command implicitly requires dependent type', type_elem.text) 781 self.markTypeRequired(type_elem.text, required) 782 else: 783 self.gen.logMsg('warn', 'command:', cmdname, 'IS NOT DEFINED') 784 785 def markRequired(self, featurename, feature, required): 786 """Require or remove features specified in the Element. 787 788 - featurename - name of the feature 789 - feature - Element for `<require>` or `<remove>` tag 790 - required - boolean (to tag features as required or not)""" 791 self.gen.logMsg('diag', 'markRequired (feature = <too long to print>, required =', required, ')') 792 793 # Loop over types, enums, and commands in the tag 794 # @@ It would be possible to respect 'api' and 'profile' attributes 795 # in individual features, but that's not done yet. 796 for typeElem in feature.findall('type'): 797 self.markTypeRequired(typeElem.get('name'), required) 798 for enumElem in feature.findall('enum'): 799 self.markEnumRequired(enumElem.get('name'), required) 800 for cmdElem in feature.findall('command'): 801 self.markCmdRequired(cmdElem.get('name'), required) 802 803 # Extensions may need to extend existing commands or other items in the future. 804 # So, look for extend tags. 805 for extendElem in feature.findall('extend'): 806 extendType = extendElem.get('type') 807 if extendType == 'command': 808 commandName = extendElem.get('name') 809 successExtends = extendElem.get('successcodes') 810 if successExtends is not None: 811 for success in successExtends.split(','): 812 self.commandextensionsuccesses.append(self.commandextensiontuple(command=commandName, 813 value=success, 814 extension=featurename)) 815 errorExtends = extendElem.get('errorcodes') 816 if errorExtends is not None: 817 for error in errorExtends.split(','): 818 self.commandextensionerrors.append(self.commandextensiontuple(command=commandName, 819 value=error, 820 extension=featurename)) 821 else: 822 self.gen.logMsg('warn', 'extend type:', extendType, 'IS NOT SUPPORTED') 823 824 def getAlias(self, elem, dict): 825 """Check for an alias in the same require block. 826 827 - elem - Element to check for an alias""" 828 829 # Try to find an alias 830 alias = elem.get('alias') 831 if alias is None: 832 name = elem.get('name') 833 typeinfo = self.lookupElementInfo(name, dict) 834 alias = typeinfo.elem.get('alias') 835 836 return alias 837 838 def checkForCorrectionAliases(self, alias, require, tag): 839 """Check for an alias in the same require block. 840 841 - alias - String name of the alias 842 - require - `<require>` block from the registry 843 - tag - tag to look for in the require block""" 844 845 if alias and require.findall(tag + "[@name='" + alias + "']"): 846 return True 847 848 return False 849 850 def fillFeatureDictionary(self, interface, featurename, api, profile): 851 """Capture added interfaces for a `<version>` or `<extension>`. 852 853 - interface - Element for `<version>` or `<extension>`, containing 854 `<require>` and `<remove>` tags 855 - featurename - name of the feature 856 - api - string specifying API name being generated 857 - profile - string specifying API profile being generated""" 858 859 # Explicitly initialize known types - errors for unhandled categories 860 self.gen.featureDictionary[featurename] = { 861 "enumconstant": {}, 862 "command": {}, 863 "enum": {}, 864 "struct": {}, 865 "handle": {}, 866 "basetype": {}, 867 "include": {}, 868 "define": {}, 869 "bitmask": {}, 870 "union": {}, 871 "funcpointer": {}, 872 } 873 874 # <require> marks things that are required by this version/profile 875 for require in interface.findall('require'): 876 if matchAPIProfile(api, profile, require): 877 878 # Determine the required extension or version needed for a require block 879 # Assumes that only one of these is specified 880 required_key = require.get('feature') 881 if required_key is None: 882 required_key = require.get('extension') 883 884 # Loop over types, enums, and commands in the tag 885 for typeElem in require.findall('type'): 886 typename = typeElem.get('name') 887 typeinfo = self.lookupElementInfo(typename, self.typedict) 888 889 if typeinfo: 890 # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible. 891 alias = self.getAlias(typeElem, self.typedict) 892 if not self.checkForCorrectionAliases(alias, require, 'type'): 893 # Resolve the type info to the actual type, so we get an accurate read for 'structextends' 894 while alias: 895 typeinfo = self.lookupElementInfo(alias, self.typedict) 896 alias = typeinfo.elem.get('alias') 897 898 typecat = typeinfo.elem.get('category') 899 typeextends = typeinfo.elem.get('structextends') 900 if not required_key in self.gen.featureDictionary[featurename][typecat]: 901 self.gen.featureDictionary[featurename][typecat][required_key] = {} 902 if not typeextends in self.gen.featureDictionary[featurename][typecat][required_key]: 903 self.gen.featureDictionary[featurename][typecat][required_key][typeextends] = [] 904 self.gen.featureDictionary[featurename][typecat][required_key][typeextends].append(typename) 905 906 for enumElem in require.findall('enum'): 907 enumname = enumElem.get('name') 908 typeinfo = self.lookupElementInfo(enumname, self.enumdict) 909 910 # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible. 911 alias = self.getAlias(enumElem, self.enumdict) 912 if not self.checkForCorrectionAliases(alias, require, 'enum'): 913 enumextends = enumElem.get('extends') 914 if not required_key in self.gen.featureDictionary[featurename]['enumconstant']: 915 self.gen.featureDictionary[featurename]['enumconstant'][required_key] = {} 916 if not enumextends in self.gen.featureDictionary[featurename]['enumconstant'][required_key]: 917 self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends] = [] 918 self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends].append(enumname) 919 920 for cmdElem in require.findall('command'): 921 922 # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible. 923 alias = self.getAlias(cmdElem, self.cmddict) 924 if not self.checkForCorrectionAliases(alias, require, 'command'): 925 if not required_key in self.gen.featureDictionary[featurename]['command']: 926 self.gen.featureDictionary[featurename]['command'][required_key] = [] 927 self.gen.featureDictionary[featurename]['command'][required_key].append(cmdElem.get('name')) 928 929 930 def requireAndRemoveFeatures(self, interface, featurename, api, profile): 931 """Process `<require>` and `<remove>` tags for a `<version>` or `<extension>`. 932 933 - interface - Element for `<version>` or `<extension>`, containing 934 `<require>` and `<remove>` tags 935 - featurename - name of the feature 936 - api - string specifying API name being generated 937 - profile - string specifying API profile being generated""" 938 # <require> marks things that are required by this version/profile 939 for feature in interface.findall('require'): 940 if matchAPIProfile(api, profile, feature): 941 self.markRequired(featurename, feature, True) 942 # <remove> marks things that are removed by this version/profile 943 for feature in interface.findall('remove'): 944 if matchAPIProfile(api, profile, feature): 945 self.markRequired(featurename, feature, False) 946 947 def assignAdditionalValidity(self, interface, api, profile): 948 # Loop over all usage inside all <require> tags. 949 for feature in interface.findall('require'): 950 if matchAPIProfile(api, profile, feature): 951 for v in feature.findall('usage'): 952 if v.get('command'): 953 self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v)) 954 if v.get('struct'): 955 self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v)) 956 957 # Loop over all usage inside all <remove> tags. 958 for feature in interface.findall('remove'): 959 if matchAPIProfile(api, profile, feature): 960 for v in feature.findall('usage'): 961 if v.get('command'): 962 self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v)) 963 if v.get('struct'): 964 self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v)) 965 966 def generateFeature(self, fname, ftype, dictionary): 967 """Generate a single type / enum group / enum / command, 968 and all its dependencies as needed. 969 970 - fname - name of feature (`<type>`/`<enum>`/`<command>`) 971 - ftype - type of feature, 'type' | 'enum' | 'command' 972 - dictionary - of *Info objects - self.{type|enum|cmd}dict""" 973 974 self.gen.logMsg('diag', 'generateFeature: generating', ftype, fname) 975 f = self.lookupElementInfo(fname, dictionary) 976 if f is None: 977 # No such feature. This is an error, but reported earlier 978 self.gen.logMsg('diag', 'No entry found for feature', fname, 979 'returning!') 980 return 981 982 # If feature isn't required, or has already been declared, return 983 if not f.required: 984 self.gen.logMsg('diag', 'Skipping', ftype, fname, '(not required)') 985 return 986 if f.declared: 987 self.gen.logMsg('diag', 'Skipping', ftype, fname, '(already declared)') 988 return 989 # Always mark feature declared, as though actually emitted 990 f.declared = True 991 992 # Determine if this is an alias, and of what, if so 993 alias = f.elem.get('alias') 994 if alias: 995 self.gen.logMsg('diag', fname, 'is an alias of', alias) 996 997 # Pull in dependent declaration(s) of the feature. 998 # For types, there may be one type in the 'requires' attribute of 999 # the element, one in the 'alias' attribute, and many in 1000 # embedded <type> and <enum> tags within the element. 1001 # For commands, there may be many in <type> tags within the element. 1002 # For enums, no dependencies are allowed (though perhaps if you 1003 # have a uint64 enum, it should require that type). 1004 genProc = None 1005 followupFeature = None 1006 if ftype == 'type': 1007 genProc = self.gen.genType 1008 1009 # Generate type dependencies in 'alias' and 'requires' attributes 1010 if alias: 1011 self.generateFeature(alias, 'type', self.typedict) 1012 requires = f.elem.get('requires') 1013 if requires: 1014 self.gen.logMsg('diag', 'Generating required dependent type', 1015 requires) 1016 self.generateFeature(requires, 'type', self.typedict) 1017 1018 # Generate types used in defining this type (e.g. in nested 1019 # <type> tags) 1020 # Look for <type> in entire <command> tree, 1021 # not just immediate children 1022 for subtype in f.elem.findall('.//type'): 1023 self.gen.logMsg('diag', 'Generating required dependent <type>', 1024 subtype.text) 1025 self.generateFeature(subtype.text, 'type', self.typedict) 1026 1027 # Generate enums used in defining this type, for example in 1028 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 1029 for subtype in f.elem.findall('.//enum'): 1030 self.gen.logMsg('diag', 'Generating required dependent <enum>', 1031 subtype.text) 1032 self.generateFeature(subtype.text, 'enum', self.enumdict) 1033 1034 # If the type is an enum group, look up the corresponding 1035 # group in the group dictionary and generate that instead. 1036 if f.elem.get('category') == 'enum': 1037 self.gen.logMsg('diag', 'Type', fname, 'is an enum group, so generate that instead') 1038 group = self.lookupElementInfo(fname, self.groupdict) 1039 if alias is not None: 1040 # An alias of another group name. 1041 # Pass to genGroup with 'alias' parameter = aliased name 1042 self.gen.logMsg('diag', 'Generating alias', fname, 1043 'for enumerated type', alias) 1044 # Now, pass the *aliased* GroupInfo to the genGroup, but 1045 # with an additional parameter which is the alias name. 1046 genProc = self.gen.genGroup 1047 f = self.lookupElementInfo(alias, self.groupdict) 1048 elif group is None: 1049 self.gen.logMsg('warn', 'Skipping enum type', fname, 1050 ': No matching enumerant group') 1051 return 1052 else: 1053 genProc = self.gen.genGroup 1054 f = group 1055 1056 # @ The enum group is not ready for generation. At this 1057 # @ point, it contains all <enum> tags injected by 1058 # @ <extension> tags without any verification of whether 1059 # @ they're required or not. It may also contain 1060 # @ duplicates injected by multiple consistent 1061 # @ definitions of an <enum>. 1062 1063 # @ Pass over each enum, marking its enumdict[] entry as 1064 # @ required or not. Mark aliases of enums as required, 1065 # @ too. 1066 1067 enums = group.elem.findall('enum') 1068 1069 self.gen.logMsg('diag', 'generateFeature: checking enums for group', fname) 1070 1071 # Check for required enums, including aliases 1072 # LATER - Check for, report, and remove duplicates? 1073 enumAliases = [] 1074 for elem in enums: 1075 name = elem.get('name') 1076 1077 required = False 1078 1079 extname = elem.get('extname') 1080 version = elem.get('version') 1081 if extname is not None: 1082 # 'supported' attribute was injected when the <enum> element was 1083 # moved into the <enums> group in Registry.parseTree() 1084 if self.genOpts.defaultExtensions == elem.get('supported'): 1085 required = True 1086 elif re.match(self.genOpts.addExtensions, extname) is not None: 1087 required = True 1088 elif version is not None: 1089 required = re.match(self.genOpts.emitversions, version) is not None 1090 else: 1091 required = True 1092 1093 self.gen.logMsg('diag', '* required =', required, 'for', name) 1094 if required: 1095 # Mark this element as required (in the element, not the EnumInfo) 1096 elem.set('required', 'true') 1097 # If it's an alias, track that for later use 1098 enumAlias = elem.get('alias') 1099 if enumAlias: 1100 enumAliases.append(enumAlias) 1101 for elem in enums: 1102 name = elem.get('name') 1103 if name in enumAliases: 1104 elem.set('required', 'true') 1105 self.gen.logMsg('diag', '* also need to require alias', name) 1106 if f.elem.get('category') == 'bitmask': 1107 followupFeature = f.elem.get('bitvalues') 1108 elif ftype == 'command': 1109 # Generate command dependencies in 'alias' attribute 1110 if alias: 1111 self.generateFeature(alias, 'command', self.cmddict) 1112 1113 genProc = self.gen.genCmd 1114 for type_elem in f.elem.findall('.//type'): 1115 depname = type_elem.text 1116 self.gen.logMsg('diag', 'Generating required parameter type', 1117 depname) 1118 self.generateFeature(depname, 'type', self.typedict) 1119 elif ftype == 'enum': 1120 # Generate enum dependencies in 'alias' attribute 1121 if alias: 1122 self.generateFeature(alias, 'enum', self.enumdict) 1123 genProc = self.gen.genEnum 1124 1125 # Actually generate the type only if emitting declarations 1126 if self.emitFeatures: 1127 self.gen.logMsg('diag', 'Emitting', ftype, 'decl for', fname) 1128 genProc(f, fname, alias) 1129 else: 1130 self.gen.logMsg('diag', 'Skipping', ftype, fname, 1131 '(should not be emitted)') 1132 1133 if followupFeature: 1134 self.gen.logMsg('diag', 'Generating required bitvalues <enum>', 1135 followupFeature) 1136 self.generateFeature(followupFeature, "type", self.typedict) 1137 1138 def generateRequiredInterface(self, interface): 1139 """Generate all interfaces required by an API version or extension. 1140 1141 - interface - Element for `<version>` or `<extension>`""" 1142 1143 # Loop over all features inside all <require> tags. 1144 for features in interface.findall('require'): 1145 for t in features.findall('type'): 1146 self.generateFeature(t.get('name'), 'type', self.typedict) 1147 for e in features.findall('enum'): 1148 # If this is an enum extending an enumerated type, don't 1149 # generate it - this has already been done in reg.parseTree, 1150 # by copying this element into the enumerated type. 1151 enumextends = e.get('extends') 1152 if not enumextends: 1153 self.generateFeature(e.get('name'), 'enum', self.enumdict) 1154 for c in features.findall('command'): 1155 self.generateFeature(c.get('name'), 'command', self.cmddict) 1156 1157 def generateSpirv(self, spirv, dictionary): 1158 if spirv is None: 1159 self.gen.logMsg('diag', 'No entry found for element', name, 1160 'returning!') 1161 return 1162 1163 name = spirv.elem.get('name') 1164 # No known alias for spirv elements 1165 alias = None 1166 if spirv.emit: 1167 genProc = self.gen.genSpirv 1168 genProc(spirv, name, alias) 1169 1170 def apiGen(self): 1171 """Generate interface for specified versions using the current 1172 generator and generator options""" 1173 1174 self.gen.logMsg('diag', '*******************************************') 1175 self.gen.logMsg('diag', ' Registry.apiGen file:', self.genOpts.filename, 1176 'api:', self.genOpts.apiname, 1177 'profile:', self.genOpts.profile) 1178 self.gen.logMsg('diag', '*******************************************') 1179 1180 # Reset required/declared flags for all features 1181 self.apiReset() 1182 1183 # Compile regexps used to select versions & extensions 1184 regVersions = re.compile(self.genOpts.versions) 1185 regEmitVersions = re.compile(self.genOpts.emitversions) 1186 regAddExtensions = re.compile(self.genOpts.addExtensions) 1187 regRemoveExtensions = re.compile(self.genOpts.removeExtensions) 1188 regEmitExtensions = re.compile(self.genOpts.emitExtensions) 1189 regEmitSpirv = re.compile(self.genOpts.emitSpirv) 1190 1191 # Get all matching API feature names & add to list of FeatureInfo 1192 # Note we used to select on feature version attributes, not names. 1193 features = [] 1194 apiMatch = False 1195 for key in self.apidict: 1196 fi = self.apidict[key] 1197 api = fi.elem.get('api') 1198 if apiNameMatch(self.genOpts.apiname, api): 1199 apiMatch = True 1200 if regVersions.match(fi.name): 1201 # Matches API & version #s being generated. Mark for 1202 # emission and add to the features[] list . 1203 # @@ Could use 'declared' instead of 'emit'? 1204 fi.emit = (regEmitVersions.match(fi.name) is not None) 1205 features.append(fi) 1206 if not fi.emit: 1207 self.gen.logMsg('diag', 'NOT tagging feature api =', api, 1208 'name =', fi.name, 'version =', fi.version, 1209 'for emission (does not match emitversions pattern)') 1210 else: 1211 self.gen.logMsg('diag', 'Including feature api =', api, 1212 'name =', fi.name, 'version =', fi.version, 1213 'for emission (matches emitversions pattern)') 1214 else: 1215 self.gen.logMsg('diag', 'NOT including feature api =', api, 1216 'name =', fi.name, 'version =', fi.version, 1217 '(does not match requested versions)') 1218 else: 1219 self.gen.logMsg('diag', 'NOT including feature api =', api, 1220 'name =', fi.name, 1221 '(does not match requested API)') 1222 if not apiMatch: 1223 self.gen.logMsg('warn', 'No matching API versions found!') 1224 1225 # Get all matching extensions, in order by their extension number, 1226 # and add to the list of features. 1227 # Start with extensions tagged with 'api' pattern matching the API 1228 # being generated. Add extensions matching the pattern specified in 1229 # regExtensions, then remove extensions matching the pattern 1230 # specified in regRemoveExtensions 1231 for (extName, ei) in sorted(self.extdict.items(), key=lambda x: x[1].number if x[1].number is not None else '0'): 1232 extName = ei.name 1233 include = False 1234 1235 # Include extension if defaultExtensions is not None and is 1236 # exactly matched by the 'supported' attribute. 1237 if apiNameMatch(self.genOpts.defaultExtensions, 1238 ei.elem.get('supported')): 1239 self.gen.logMsg('diag', 'Including extension', 1240 extName, "(defaultExtensions matches the 'supported' attribute)") 1241 include = True 1242 1243 # Include additional extensions if the extension name matches 1244 # the regexp specified in the generator options. This allows 1245 # forcing extensions into an interface even if they're not 1246 # tagged appropriately in the registry. 1247 if regAddExtensions.match(extName) is not None: 1248 self.gen.logMsg('diag', 'Including extension', 1249 extName, '(matches explicitly requested extensions to add)') 1250 include = True 1251 # Remove extensions if the name matches the regexp specified 1252 # in generator options. This allows forcing removal of 1253 # extensions from an interface even if they're tagged that 1254 # way in the registry. 1255 if regRemoveExtensions.match(extName) is not None: 1256 self.gen.logMsg('diag', 'Removing extension', 1257 extName, '(matches explicitly requested extensions to remove)') 1258 include = False 1259 1260 # If the extension is to be included, add it to the 1261 # extension features list. 1262 if include: 1263 ei.emit = (regEmitExtensions.match(extName) is not None) 1264 features.append(ei) 1265 if not ei.emit: 1266 self.gen.logMsg('diag', 'NOT tagging extension', 1267 extName, 1268 'for emission (does not match emitextensions pattern)') 1269 1270 # Hack - can be removed when validity generator goes away 1271 # (Jon) I'm not sure what this does, or if it should respect 1272 # the ei.emit flag above. 1273 self.requiredextensions.append(extName) 1274 else: 1275 self.gen.logMsg('diag', 'NOT including extension', 1276 extName, '(does not match api attribute or explicitly requested extensions)') 1277 1278 # Add all spirv elements to list 1279 # generators decide to emit them all or not 1280 # Currently no filtering as no client of these elements needs filtering 1281 spirvexts = [] 1282 for key in self.spirvextdict: 1283 si = self.spirvextdict[key] 1284 si.emit = (regEmitSpirv.match(key) is not None) 1285 spirvexts.append(si) 1286 spirvcaps = [] 1287 for key in self.spirvcapdict: 1288 si = self.spirvcapdict[key] 1289 si.emit = (regEmitSpirv.match(key) is not None) 1290 spirvcaps.append(si) 1291 1292 # Sort the features list, if a sort procedure is defined 1293 if self.genOpts.sortProcedure: 1294 self.genOpts.sortProcedure(features) 1295 # print('sortProcedure ->', [f.name for f in features]) 1296 1297 # Pass 1: loop over requested API versions and extensions tagging 1298 # types/commands/features as required (in an <require> block) or no 1299 # longer required (in an <remove> block). It is possible to remove 1300 # a feature in one version and restore it later by requiring it in 1301 # a later version. 1302 # If a profile other than 'None' is being generated, it must 1303 # match the profile attribute (if any) of the <require> and 1304 # <remove> tags. 1305 self.gen.logMsg('diag', 'PASS 1: TAG FEATURES') 1306 for f in features: 1307 self.gen.logMsg('diag', 'PASS 1: Tagging required and removed features for', 1308 f.name) 1309 self.fillFeatureDictionary(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) 1310 self.requireAndRemoveFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) 1311 self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile) 1312 1313 # Pass 2: loop over specified API versions and extensions printing 1314 # declarations for required things which haven't already been 1315 # generated. 1316 self.gen.logMsg('diag', 'PASS 2: GENERATE INTERFACES FOR FEATURES') 1317 self.gen.beginFile(self.genOpts) 1318 for f in features: 1319 self.gen.logMsg('diag', 'PASS 2: Generating interface for', 1320 f.name) 1321 emit = self.emitFeatures = f.emit 1322 if not emit: 1323 self.gen.logMsg('diag', 'PASS 2: NOT declaring feature', 1324 f.elem.get('name'), 'because it is not tagged for emission') 1325 # Generate the interface (or just tag its elements as having been 1326 # emitted, if they haven't been). 1327 self.gen.beginFeature(f.elem, emit) 1328 self.generateRequiredInterface(f.elem) 1329 self.gen.endFeature() 1330 # Generate spirv elements 1331 for s in spirvexts: 1332 self.generateSpirv(s, self.spirvextdict) 1333 for s in spirvcaps: 1334 self.generateSpirv(s, self.spirvcapdict) 1335 self.gen.endFile() 1336 1337 def apiReset(self): 1338 """Reset type/enum/command dictionaries before generating another API. 1339 1340 Use between apiGen() calls to reset internal state.""" 1341 for datatype in self.typedict: 1342 self.typedict[datatype].resetState() 1343 for enum in self.enumdict: 1344 self.enumdict[enum].resetState() 1345 for cmd in self.cmddict: 1346 self.cmddict[cmd].resetState() 1347 for cmd in self.apidict: 1348 self.apidict[cmd].resetState() 1349 1350 def validateGroups(self): 1351 """Validate `group=` attributes on `<param>` and `<proto>` tags. 1352 1353 Check that `group=` attributes match actual groups""" 1354 # Keep track of group names not in <group> tags 1355 badGroup = {} 1356 self.gen.logMsg('diag', 'VALIDATING GROUP ATTRIBUTES') 1357 for cmd in self.reg.findall('commands/command'): 1358 proto = cmd.find('proto') 1359 # funcname = cmd.find('proto/name').text 1360 group = proto.get('group') 1361 if group is not None and group not in self.groupdict: 1362 # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group) 1363 if group not in badGroup: 1364 badGroup[group] = 1 1365 else: 1366 badGroup[group] = badGroup[group] + 1 1367 1368 for param in cmd.findall('param'): 1369 pname = param.find('name') 1370 if pname is not None: 1371 pname = pname.text 1372 else: 1373 pname = param.get('name') 1374 group = param.get('group') 1375 if group is not None and group not in self.groupdict: 1376 # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group) 1377 if group not in badGroup: 1378 badGroup[group] = 1 1379 else: 1380 badGroup[group] = badGroup[group] + 1 1381 1382 if badGroup: 1383 self.gen.logMsg('diag', 'SUMMARY OF UNRECOGNIZED GROUPS') 1384 for key in sorted(badGroup.keys()): 1385 self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times') 1386