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