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