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