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