• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2023 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import re
8from collections import OrderedDict, namedtuple
9from functools import reduce
10from pathlib import Path
11
12from spec_tools.conventions import ProseListFormats as plf
13from generator import OutputGenerator, write
14from spec_tools.attributes import ExternSyncEntry, LengthEntry
15from spec_tools.util import (findNamedElem, findNamedObject, findTypedElem,
16                             getElemName, getElemType)
17from spec_tools.validity import ValidityCollection, ValidityEntry
18
19
20class UnhandledCaseError(RuntimeError):
21    def __init__(self, msg=None):
22        if msg:
23            super().__init__('Got a case in the validity generator that we have not explicitly handled: ' + msg)
24        else:
25            super().__init__('Got a case in the validity generator that we have not explicitly handled.')
26
27
28def _genericIterateIntersection(a, b):
29    """Iterate through all elements in a that are also in b.
30
31    Somewhat like a set's intersection(),
32    but not type-specific so it can work with OrderedDicts, etc.
33    It also returns a generator instead of a set,
34    so you can pick a preferred container type,
35    if any.
36    """
37    return (x for x in a if x in b)
38
39
40def _make_ordered_dict(gen):
41    """Make an ordered dict (with None as the values) from a generator."""
42    return OrderedDict(((x, None) for x in gen))
43
44
45def _orderedDictIntersection(a, b):
46    return _make_ordered_dict(_genericIterateIntersection(a, b))
47
48
49def _genericIsDisjoint(a, b):
50    """Return true if nothing in a is also in b.
51
52    Like a set's is_disjoint(),
53    but not type-specific so it can work with OrderedDicts, etc.
54    """
55    for _ in _genericIterateIntersection(a, b):
56        return False
57    # if we never enter the loop...
58    return True
59
60
61class ValidityOutputGenerator(OutputGenerator):
62    """ValidityOutputGenerator - subclass of OutputGenerator.
63
64    Generates AsciiDoc includes of valid usage information, for reference
65    pages and the specification. Similar to DocOutputGenerator.
66
67    ---- methods ----
68    ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for
69    OutputGenerator. Defines additional internal state.
70    ---- methods overriding base class ----
71    beginFile(genOpts)
72    endFile()
73    beginFeature(interface, emit)
74    endFeature()
75    genCmd(cmdinfo)
76    """
77
78    def __init__(self, *args, **kwargs):
79        super().__init__(*args, **kwargs)
80
81        self.currentExtension = ''
82
83        # Tracks whether we are tracing operations
84        self.trace = False
85
86    @property
87    def null(self):
88        """Preferred spelling of NULL.
89
90        Delegates to the object implementing ConventionsBase.
91        """
92        return self.conventions.null
93
94    @property
95    def structtype_member_name(self):
96        """Return name of the structure type member.
97
98        Delegates to the object implementing ConventionsBase.
99        """
100        return self.conventions.structtype_member_name
101
102    @property
103    def nextpointer_member_name(self):
104        """Return name of the structure pointer chain member.
105
106        Delegates to the object implementing ConventionsBase.
107        """
108        return self.conventions.nextpointer_member_name
109
110    def makeProseList(self, elements, fmt=plf.AND,
111                      comma_for_two_elts=False, *args, **kwargs):
112        """Make a (comma-separated) list for use in prose.
113
114        Adds a connective (by default, 'and')
115        before the last element if there are more than 1.
116
117        Optionally adds a quantifier (like 'any') before a list of 2 or more,
118        if specified by fmt.
119
120        Delegates to the object implementing ConventionsBase.
121        """
122        if not elements:
123            raise ValueError(
124                'Cannot pass an empty list if you are trying to make a prose list.')
125        return self.conventions.makeProseList(elements,
126                                              fmt,
127                                              with_verb=False,
128                                              comma_for_two_elts=comma_for_two_elts,
129                                              *args, **kwargs)
130
131    def makeProseListIs(self, elements, fmt=plf.AND,
132                        comma_for_two_elts=False, *args, **kwargs):
133        """Make a (comma-separated) list for use in prose, followed by either 'is' or 'are' as appropriate.
134
135        Adds a connective (by default, 'and')
136        before the last element if there are more than 1.
137
138        Optionally adds a quantifier (like 'any') before a list of 2 or more,
139        if specified by fmt.
140
141        Delegates to the object implementing ConventionsBase.
142        """
143        if not elements:
144            raise ValueError(
145                'Cannot pass an empty list if you are trying to make a prose list.')
146        return self.conventions.makeProseList(elements,
147                                              fmt,
148                                              with_verb=True,
149                                              comma_for_two_elts=comma_for_two_elts,
150                                              *args, **kwargs)
151
152    def makeValidityCollection(self, entity_name):
153        """Create a ValidityCollection object, passing along our Conventions."""
154        return ValidityCollection(entity_name, self.conventions)
155
156    def beginFile(self, genOpts):
157        if not genOpts.conventions:
158            raise RuntimeError(
159                'Must specify conventions object to generator options')
160        self.conventions = genOpts.conventions
161        # Vulkan says 'must: be a valid pointer' a lot, OpenXR just says
162        # 'must: be a pointer'.
163        self.valid_pointer_text = ' '.join(
164            (x for x in (self.conventions.valid_pointer_prefix, 'pointer') if x))
165        OutputGenerator.beginFile(self, genOpts)
166
167    def endFile(self):
168        OutputGenerator.endFile(self)
169
170    def beginFeature(self, interface, emit):
171        # Start processing in superclass
172        OutputGenerator.beginFeature(self, interface, emit)
173        self.currentExtension = interface.get('name')
174
175    def endFeature(self):
176        # Finish processing in superclass
177        OutputGenerator.endFeature(self)
178
179    @property
180    def struct_macro(self):
181        """Get the appropriate format macro for a structure."""
182        # delegate to conventions
183        return self.conventions.struct_macro
184
185    def makeStructName(self, name):
186        """Prepend the appropriate format macro for a structure to a structure type name."""
187        # delegate to conventions
188        return self.conventions.makeStructName(name)
189
190    def makeParameterName(self, name):
191        """Prepend the appropriate format macro for a parameter/member to a parameter name."""
192        return 'pname:' + name
193
194    def makeBaseTypeName(self, name):
195        """Prepend the appropriate format macro for a 'base type' to a type name."""
196        return 'basetype:' + name
197
198    def makeEnumerationName(self, name):
199        """Prepend the appropriate format macro for an enumeration type to a enum type name."""
200        return 'elink:' + name
201
202    def makeFlagsName(self, name):
203        """Prepend the appropriate format macro for a flags type to a flags type name."""
204        return 'tlink:' + name
205
206    def makeFuncPointerName(self, name):
207        """Prepend the appropriate format macro for a function pointer type to a type name."""
208        return 'tlink:' + name
209
210    def makeExternalTypeName(self, name):
211        """Prepend the appropriate format macro for an external type like uint32_t to a type name."""
212        # delegate to conventions
213        return self.conventions.makeExternalTypeName(name)
214
215    def makeEnumerantName(self, name):
216        """Prepend the appropriate format macro for an enumerate (value) to a enum value name."""
217        return 'ename:' + name
218
219    def writeInclude(self, directory, basename, validity: ValidityCollection,
220                     threadsafety, commandpropertiesentry=None,
221                     successcodes=None, errorcodes=None):
222        """Generate an include file.
223
224        directory - subdirectory to put file in (absolute or relative pathname)
225        basename - base name of the file
226        validity - ValidityCollection to write.
227        threadsafety - List (may be empty) of thread safety statements to write.
228        successcodes - Optional success codes to document.
229        errorcodes - Optional error codes to document.
230        """
231        # Create subdirectory, if needed
232        directory = Path(directory)
233        if not directory.is_absolute():
234            directory = Path(self.genOpts.directory) / directory
235        self.makeDir(str(directory))
236
237        # Create validity file
238        filename = str(directory / f'{basename}{self.file_suffix}')
239        self.logMsg('diag', '# Generating include file:', filename)
240
241        with open(filename, 'w', encoding='utf-8') as fp:
242            write(self.conventions.warning_comment, file=fp)
243
244            # Valid Usage
245            if validity:
246                write('.Valid Usage (Implicit)', file=fp)
247                write('****', file=fp)
248                write(validity, file=fp, end='')
249                write('****', file=fp)
250                write('', file=fp)
251
252            # Host Synchronization
253            if threadsafety:
254                # The heading of this block differs between projects, so an Asciidoc attribute is used.
255                write('.{externsynctitle}', file=fp)
256                write('****', file=fp)
257                write(threadsafety, file=fp, end='')
258                write('****', file=fp)
259                write('', file=fp)
260
261            # Command Properties - contained within a block, to avoid table numbering
262            if commandpropertiesentry:
263                write('.Command Properties', file=fp)
264                write('****', file=fp)
265                write('[options="header", width="100%"]', file=fp)
266                write('|====', file=fp)
267                write(self.makeCommandPropertiesTableHeader(), file=fp)
268                write(commandpropertiesentry, file=fp)
269                write('|====', file=fp)
270                write('****', file=fp)
271                write('', file=fp)
272
273            # Success Codes - contained within a block, to avoid table numbering
274            if successcodes or errorcodes:
275                write('.Return Codes', file=fp)
276                write('****', file=fp)
277                if successcodes:
278                    write('ifndef::doctype-manpage[]', file=fp)
279                    write('<<fundamentals-successcodes,Success>>::', file=fp)
280                    write('endif::doctype-manpage[]', file=fp)
281                    write('ifdef::doctype-manpage[]', file=fp)
282                    write('On success, this command returns::', file=fp)
283                    write('endif::doctype-manpage[]', file=fp)
284                    write(successcodes, file=fp)
285                if errorcodes:
286                    write('ifndef::doctype-manpage[]', file=fp)
287                    write('<<fundamentals-errorcodes,Failure>>::', file=fp)
288                    write('endif::doctype-manpage[]', file=fp)
289                    write('ifdef::doctype-manpage[]', file=fp)
290                    write('On failure, this command returns::', file=fp)
291                    write('endif::doctype-manpage[]', file=fp)
292                    write(errorcodes, file=fp)
293                write('****', file=fp)
294                write('', file=fp)
295
296    def paramIsStaticArray(self, param):
297        """Check if the parameter passed in is a static array."""
298        tail = param.find('name').tail
299        return tail and tail[0] == '['
300
301    def paramIsConst(self, param):
302        """Check if the parameter passed in has a type that mentions const."""
303        return param.text is not None and 'const' in param.text
304
305    def staticArrayLength(self, param):
306        """Get the length of a parameter that has been identified as a static array."""
307        paramenumsize = param.find('enum')
308        if paramenumsize is not None:
309            return paramenumsize.text
310            # TODO switch to below when cosmetic changes OK
311            # return self.makeEnumerantName(paramenumsize.text)
312
313        return param.find('name').tail[1:-1]
314
315    def getHandleDispatchableAncestors(self, typename):
316        """Get the ancestors of a handle object."""
317        ancestors = []
318        current = typename
319        while True:
320            current = self.getHandleParent(current)
321            if current is None:
322                return ancestors
323            if self.isHandleTypeDispatchable(current):
324                ancestors.append(current)
325
326    def isHandleTypeDispatchable(self, handlename):
327        """Check if a parent object is dispatchable or not."""
328        handle = self.registry.tree.find(
329            "types/type/[name='" + handlename + "'][@category='handle']")
330        if handle is not None and getElemType(handle) == 'VK_DEFINE_HANDLE':
331            return True
332        else:
333            return False
334
335    def isHandleOptional(self, param, params):
336        # Simple, if it is optional, return true
337        if param.get('optional') is not None:
338            return True
339
340        # If no validity is being generated, it usually means that validity is complex and not absolute, so say yes.
341        if param.get('noautovalidity') is not None:
342            return True
343
344        # If the parameter is an array and we have not already returned, find out if any of the len parameters are optional
345        if self.paramIsArray(param):
346            for length in LengthEntry.parse_len_from_param(param):
347                if not length.other_param_name:
348                    # do not care about constants or "null-terminated"
349                    continue
350
351                other_param = findNamedElem(params, length.other_param_name)
352                if other_param is None:
353                    self.logMsg('warn', length.other_param_name,
354                                'is listed as a length for parameter', param, 'but no such parameter exists')
355                if other_param and other_param.get('optional'):
356                    return True
357
358        return False
359
360    def makeOptionalPre(self, param):
361        # Do not generate this stub for bitflags
362        param_name = getElemName(param)
363        paramtype = getElemType(param)
364        type_category = self.getTypeCategory(paramtype)
365        is_optional = param.get('optional').split(',')[0] == 'true'
366        if type_category != 'bitmask' and is_optional:
367            if self.paramIsArray(param) or self.paramIsPointer(param):
368                optional_val = self.null
369            elif type_category == 'handle':
370                if self.isHandleTypeDispatchable(paramtype):
371                    optional_val = self.null
372                else:
373                    optional_val = 'dlink:' + self.conventions.api_prefix + 'NULL_HANDLE'
374            else:
375                optional_val = self.conventions.zero
376            return 'If {} is not {}, '.format(
377                self.makeParameterName(param_name),
378                optional_val)
379
380        return ""
381
382    def makeParamValidityPre(self, param, params, selector):
383        """Make the start of an entry for a parameter's validity, including a chunk of text if it is an array."""
384        param_name = getElemName(param)
385        paramtype = getElemType(param)
386
387        # General pre-amble. Check optionality and add stuff.
388        entry = ValidityEntry(anchor=(param_name, 'parameter'))
389        is_optional = param.get('optional') is not None and param.get('optional').split(',')[0] == 'true'
390
391        # This is for a union member, and the valid member is chosen by an enum selection
392        if selector:
393            selection = param.get('selection')
394
395            entry += 'If {} is {}, '.format(
396                self.makeParameterName(selector),
397                self.makeEnumerantName(selection))
398
399            if is_optional:
400                entry += "and "
401                optionalpre = self.makeOptionalPre(param)
402                entry += optionalpre[0].lower() + optionalpre[1:]
403
404            return entry
405
406        if self.paramIsStaticArray(param):
407            if paramtype != 'char':
408                entry += 'Each element of '
409            return entry
410
411        if self.paramIsArray(param) and param.get('len') != LengthEntry.NULL_TERMINATED_STRING:
412            # Find all the parameters that are called out as optional,
413            # so we can document that they might be zero, and the array may be ignored
414            optionallengths = []
415            for length in LengthEntry.parse_len_from_param(param):
416                if not length.other_param_name:
417                    # Only care about length entries that are parameter names
418                    continue
419
420                other_param = findNamedElem(params, length.other_param_name)
421                other_param_optional = (other_param is not None) and (
422                    other_param.get('optional') is not None)
423
424                if other_param is None or not other_param_optional:
425                    # Do not care about not-found params or non-optional params
426                    continue
427
428                if self.paramIsPointer(other_param):
429                    optionallengths.append(
430                        'the value referenced by ' + self.makeParameterName(length.other_param_name))
431                else:
432                    optionallengths.append(
433                        self.makeParameterName(length.other_param_name))
434
435            # Document that these arrays may be ignored if any of the length values are 0
436            if optionallengths or is_optional:
437                entry += 'If '
438            if optionallengths:
439                entry += self.makeProseListIs(optionallengths, fmt=plf.OR)
440                entry += ' not %s, ' % self.conventions.zero
441            # TODO enabling this in OpenXR, as used in Vulkan, causes nonsensical things like
442            # "If pname:propertyCapacityInput is not `0`, and pname:properties is not `NULL`, pname:properties must: be a pointer to an array of pname:propertyCapacityInput slink:XrApiLayerProperties structures"
443            if optionallengths and is_optional:
444                entry += 'and '
445            if is_optional:
446                entry += self.makeParameterName(param_name)
447                # TODO switch when cosmetic changes OK
448                # entry += ' is not {}, '.format(self.null)
449                entry += ' is not `NULL`, '
450            return entry
451
452        if param.get('optional'):
453            entry += self.makeOptionalPre(param)
454            return entry
455
456        # If none of the early returns happened, we at least return an empty
457        # entry with an anchor.
458        return entry
459
460    def createValidationLineForParameterImpl(self, blockname, param, params, typetext, selector, parentname):
461        """Make the generic validity portion used for all parameters.
462
463        May return None if nothing to validate.
464        """
465        if param.get('noautovalidity') is not None:
466            return None
467
468        validity = self.makeValidityCollection(blockname)
469        param_name = getElemName(param)
470        paramtype = getElemType(param)
471
472        entry = self.makeParamValidityPre(param, params, selector)
473
474        # pAllocator is not supported in VulkanSC and must always be NULL
475        if self.conventions.xml_api_name == "vulkansc" and param_name == 'pAllocator':
476            entry = ValidityEntry(anchor=(param_name, 'null'))
477            entry += 'pname:pAllocator must: be `NULL`'
478            return entry
479
480        # This is for a child member of a union
481        if selector:
482            entry += 'the {} member of {} must: be '.format(self.makeParameterName(param_name), self.makeParameterName(parentname))
483        else:
484            entry += '{} must: be '.format(self.makeParameterName(param_name))
485
486        if self.paramIsStaticArray(param) and paramtype == 'char':
487            # TODO this is a minor hack to determine if this is a command parameter or a struct member
488            if self.paramIsConst(param) or blockname.startswith(self.conventions.type_prefix):
489                entry += 'a null-terminated UTF-8 string whose length is less than or equal to '
490                entry += self.staticArrayLength(param)
491            else:
492                # This is a command's output parameter
493                entry += 'a character array of length %s ' % self.staticArrayLength(param)
494            validity += entry
495            return validity
496
497        elif self.paramIsArray(param):
498            # Arrays. These are hard to get right, apparently
499
500            lengths = LengthEntry.parse_len_from_param(param)
501
502            for i, length in enumerate(LengthEntry.parse_len_from_param(param)):
503                if i == 0:
504                    # If the first index, make it singular.
505                    entry += 'a '
506                    array_text = 'an array'
507                    pointer_text = self.valid_pointer_text
508                else:
509                    array_text = 'arrays'
510                    pointer_text = self.valid_pointer_text + 's'
511
512                if length.null_terminated:
513                    # This should always be the last thing.
514                    # If it ever is not for some bizarre reason, then this
515                    # will need some massaging.
516                    entry += 'null-terminated '
517                elif length.number == 1:
518                    entry += pointer_text
519                    entry += ' to '
520                else:
521                    entry += pointer_text
522                    entry += ' to '
523                    entry += array_text
524                    entry += ' of '
525                    # Handle equations, which are currently denoted with latex
526                    if length.math:
527                        # Handle equations, which are currently denoted with latex
528                        entry += str(length)
529                    else:
530                        entry += self.makeParameterName(str(length))
531                    entry += ' '
532
533            # Void pointers do not actually point at anything - remove the word "to"
534            if paramtype == 'void':
535                if lengths[-1].number == 1:
536                    if len(lengths) > 1:
537                        # Take care of the extra s added by the post array chunk function. #HACK#
538                        entry.drop_end(5)
539                    else:
540                        entry.drop_end(4)
541
542                    # This has not been hit, so this has not been tested recently.
543                    raise UnhandledCaseError(
544                        "Got void pointer param/member with last length 1")
545                else:
546                    # An array of void values is a byte array.
547                    entry += 'byte'
548
549            elif paramtype == 'char':
550                # A null terminated array of chars is a string
551                if lengths[-1].null_terminated:
552                    entry += 'UTF-8 string'
553                else:
554                    # Else it is just a bunch of chars
555                    entry += 'char value'
556
557            elif self.paramIsConst(param):
558                # If a value is "const" that means it will not get modified,
559                # so it must be valid going into the function.
560                if 'const' in param.text:
561
562                    if not self.isStructAlwaysValid(paramtype):
563                        entry += 'valid '
564
565            # Check if the array elements are optional
566            array_element_optional = param.get('optional') is not None    \
567                      and len(param.get('optional').split(',')) == len(LengthEntry.parse_len_from_param(param)) + 1 \
568                      and param.get('optional').split(',')[-1] == 'true'
569            if array_element_optional and self.getTypeCategory(paramtype) != 'bitmask': # bitmask is handled later
570                entry += 'or dlink:' + self.conventions.api_prefix + 'NULL_HANDLE '
571
572            entry += typetext
573
574            # pluralize
575            if len(lengths) > 1 or (lengths[0] != 1 and not lengths[0].null_terminated):
576                entry += 's'
577
578            return self.handleRequiredBitmask(blockname, param, paramtype, entry, 'true' if array_element_optional else None)
579
580        if self.paramIsPointer(param):
581            # Handle pointers - which are really special case arrays (i.e.
582            # they do not have a length)
583            # TODO  should do something here if someone ever uses some intricate comma-separated `optional`
584            pointercount = param.find('type').tail.count('*')
585
586            # Treat void* as an int
587            if paramtype == 'void':
588                optional = param.get('optional')
589                # If there is only void*, it is just optional int - we do not need any language.
590                if pointercount == 1 and optional is not None:
591                    return None  # early return
592                # Treat the inner-most void* as an int
593                pointercount -= 1
594
595            # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that.
596            entry += 'a '
597            entry += (self.valid_pointer_text + ' to a ') * pointercount
598
599            # Handle void* and pointers to it
600            if paramtype == 'void':
601                if optional is None or optional.split(',')[pointercount]:
602                    # The last void* is just optional int (e.g. to be filled by the impl.)
603                    typetext = 'pointer value'
604
605            # If a value is "const" that means it will not get modified, so
606            # it must be valid going into the function.
607            elif self.paramIsConst(param) and paramtype != 'void':
608                entry += 'valid '
609
610            entry += typetext
611            return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional'))
612
613        # Add additional line for non-optional bitmasks
614        if self.getTypeCategory(paramtype) == 'bitmask':
615            # TODO does not really handle if someone tries something like optional="true,false"
616            # TODO OpenXR has 0 or a valid combination of flags, for optional things.
617            # Vulkan does not...
618            # isMandatory = param.get('optional') is None
619            # if not isMandatory:
620            #     entry += self.conventions.zero
621            #     entry += ' or '
622            # Non-pointer, non-optional things must be valid
623            entry += 'a valid {}'.format(typetext)
624
625            return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional'))
626
627        # Non-pointer, non-optional things must be valid
628        entry += 'a valid {}'.format(typetext)
629        return entry
630
631    def handleRequiredBitmask(self, blockname, param, paramtype, entry, optional):
632        # TODO does not really handle if someone tries something like optional="true,false"
633        if self.getTypeCategory(paramtype) != 'bitmask' or optional == 'true':
634            return entry
635        if self.paramIsPointer(param) and not self.paramIsArray(param):
636            # This is presumably an output parameter
637            return entry
638
639        param_name = getElemName(param)
640        # If mandatory, then we need two entries instead of just one.
641        validity = self.makeValidityCollection(blockname)
642        validity += entry
643
644        entry2 = ValidityEntry(anchor=(param_name, 'requiredbitmask'))
645        if self.paramIsArray(param):
646            entry2 += 'Each element of '
647        entry2 += '{} must: not be {}'.format(
648            self.makeParameterName(param_name), self.conventions.zero)
649        validity += entry2
650        return validity
651
652    def createValidationLineForParameter(self, blockname, param, params, typecategory, selector, parentname):
653        """Make an entire validation entry for a given parameter."""
654        param_name = getElemName(param)
655        paramtype = getElemType(param)
656
657        is_array = self.paramIsArray(param)
658        is_pointer = self.paramIsPointer(param)
659        needs_recursive_validity = (is_array
660                                    or is_pointer
661                                    or not self.isStructAlwaysValid(paramtype))
662        typetext = None
663        if paramtype in ('void', 'char'):
664            # Chars and void are special cases - we call the impl function,
665            # but do not use the typetext.
666            # A null-terminated char array is a string, else it is chars.
667            # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular
668            typetext = ''
669
670        elif typecategory == 'bitmask':
671            bitsname = paramtype.replace('Flags', 'FlagBits')
672            bitselem = self.registry.tree.find("enums[@name='" + bitsname + "']")
673
674            # If bitsname is an alias, then use the alias to get bitselem.
675            typeElem = self.registry.lookupElementInfo(bitsname, self.registry.typedict)
676            if typeElem is not None:
677                alias = self.registry.getAlias(typeElem.elem, self.registry.typedict)
678                if alias is not None:
679                    bitselem = self.registry.tree.find("enums[@name='" + alias + "']")
680
681            if bitselem is None or len(bitselem.findall('enum[@required="true"]')) == 0:
682                # Empty bit mask: presumably just a placeholder (or only in
683                # an extension not enabled for this build)
684                entry = ValidityEntry(
685                    anchor=(param_name, 'zerobitmask'))
686                entry += self.makeParameterName(param_name)
687                entry += ' must: be '
688                entry += self.conventions.zero
689                # Early return
690                return entry
691
692            is_const = self.paramIsConst(param)
693
694            if is_array:
695                if is_const:
696                    # input an array of bitmask values
697                    template = 'combinations of {bitsname} value'
698                else:
699                    template = '{paramtype} value'
700            elif is_pointer:
701                if is_const:
702                    template = 'combination of {bitsname} values'
703                else:
704                    template = '{paramtype} value'
705            else:
706                template = 'combination of {bitsname} values'
707
708            # The above few cases all use makeEnumerationName, just with different context.
709            typetext = template.format(
710                bitsname=self.makeEnumerationName(bitsname),
711                paramtype=self.makeFlagsName(paramtype))
712
713        elif typecategory == 'handle':
714            typetext = '{} handle'.format(self.makeStructName(paramtype))
715
716        elif typecategory == 'enum':
717            typetext = '{} value'.format(self.makeEnumerationName(paramtype))
718
719        elif typecategory == 'funcpointer':
720            typetext = '{} value'.format(self.makeFuncPointerName(paramtype))
721
722        elif typecategory == 'struct':
723            if needs_recursive_validity:
724                typetext = '{} structure'.format(
725                    self.makeStructName(paramtype))
726
727        elif typecategory == 'union':
728            if needs_recursive_validity:
729                typetext = '{} union'.format(self.makeStructName(paramtype))
730
731        elif self.paramIsArray(param) or self.paramIsPointer(param):
732            # TODO sync cosmetic changes from OpenXR?
733            if typecategory is None:
734                typetext = f'code:{paramtype} value'
735            else:
736                typetext = '{} value'.format(self.makeBaseTypeName(paramtype))
737
738        elif typecategory is None:
739            if not self.isStructAlwaysValid(paramtype):
740                typetext = '{} value'.format(
741                    self.makeExternalTypeName(paramtype))
742
743            # "a valid uint32_t value" does not make much sense.
744            pass
745
746        # If any of the above conditions matched and set typetext,
747        # we call using it.
748        if typetext is not None:
749            return self.createValidationLineForParameterImpl(
750                blockname, param, params, typetext, selector, parentname)
751        return None
752
753    def makeHandleValidityParent(self, param, params):
754        """Make a validity entry for a handle's parent object.
755
756        Creates 'parent' VUID.
757        """
758        param_name = getElemName(param)
759        paramtype = getElemType(param)
760
761        # Iterate up the handle parent hierarchy for the first parameter of
762        # a parent type.
763        # This enables cases where a more distant ancestor is present, such
764        # as VkDevice and VkCommandBuffer (but no direct parent
765        # VkCommandPool).
766
767        while True:
768            # If we run out of ancestors, give up
769            handleparent = self.getHandleParent(paramtype)
770            if handleparent is None:
771                if self.trace:
772                    print(f'makeHandleValidityParent:{param_name} has no handle parent, skipping')
773                return None
774
775            # Look for a parameter of the ancestor type
776            otherparam = findTypedElem(params, handleparent)
777            if otherparam is not None:
778                break
779
780            # Continue up the hierarchy
781            paramtype = handleparent
782
783        parent_name = getElemName(otherparam)
784        entry = ValidityEntry(anchor=(param_name, 'parent'))
785
786        is_optional = self.isHandleOptional(param, params)
787
788        if self.paramIsArray(param):
789            template = 'Each element of {}'
790            if is_optional:
791                template += ' that is a valid handle'
792        elif is_optional:
793            template = 'If {} is a valid handle, it'
794        else:
795            # not optional, not an array. Just say the parameter name.
796            template = '{}'
797
798        entry += template.format(self.makeParameterName(param_name))
799
800        entry += ' must: have been created, allocated, or retrieved from {}'.format(
801            self.makeParameterName(parent_name))
802
803        return entry
804
805    def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params):
806        """Make an asciidoc validity entry for a common ancestors between handles.
807
808        Only handles parent validity for signatures taking multiple handles
809        any ancestors also being supplied to this function.
810        (e.g. "Each of x, y, and z must: come from the same slink:ParentHandle")
811        See self.makeAsciiDocHandleParent() for instances where the parent
812        handle is named and also passed.
813
814        Creates 'commonparent' VUID.
815        """
816        # TODO Replace with refactored code from OpenXR
817        entry = None
818
819        if len(handles) > 1:
820            ancestormap = {}
821            anyoptional = False
822            # Find all the ancestors
823            for param in handles:
824                paramtype = getElemType(param)
825
826                if not self.paramIsPointer(param) or (param.text and 'const' in param.text):
827                    ancestors = self.getHandleDispatchableAncestors(paramtype)
828
829                    ancestormap[param] = ancestors
830
831                    anyoptional |= self.isHandleOptional(param, params)
832
833            # Remove redundant ancestor lists
834            for param in handles:
835                paramtype = getElemType(param)
836
837                removals = []
838                for ancestors in ancestormap.items():
839                    if paramtype in ancestors[1]:
840                        removals.append(ancestors[0])
841
842                if removals != []:
843                    for removal in removals:
844                        del(ancestormap[removal])
845
846            # Intersect
847
848            if len(ancestormap.values()) > 1:
849                current = list(ancestormap.values())[0]
850                for ancestors in list(ancestormap.values())[1:]:
851                    current = [val for val in current if val in ancestors]
852
853                if len(current) > 0:
854                    commonancestor = current[0]
855
856                    if len(ancestormap.keys()) > 1:
857
858                        entry = ValidityEntry(anchor=('commonparent',))
859
860                        parametertexts = []
861                        for param in ancestormap.keys():
862                            param_name = getElemName(param)
863                            parametertext = self.makeParameterName(param_name)
864                            if self.paramIsArray(param):
865                                parametertext = 'the elements of ' + parametertext
866                            parametertexts.append(parametertext)
867
868                        parametertexts.sort()
869
870                        if len(parametertexts) > 2:
871                            entry += 'Each of '
872                        else:
873                            entry += 'Both of '
874
875                        entry += self.makeProseList(parametertexts,
876                                                    comma_for_two_elts=True)
877                        if anyoptional is True:
878                            entry += ' that are valid handles of non-ignored parameters'
879                        entry += ' must: have been created, allocated, or retrieved from the same '
880                        entry += self.makeStructName(commonancestor)
881
882        return entry
883
884    def makeStructureTypeFromName(self, structname):
885        """Create text for a structure type name, like ename:VK_STRUCTURE_TYPE_CREATE_INSTANCE_INFO"""
886        return self.makeEnumerantName(self.conventions.generate_structure_type_from_name(structname))
887
888    def makeStructureTypeValidity(self, structname):
889        """Generate an validity line for the type value of a struct.
890
891        Creates VUID named like the member name.
892        """
893        info = self.registry.typedict.get(structname)
894        assert(info is not None)
895
896        # If this fails (meaning we have something other than a struct in here),
897        # then the caller is wrong:
898        # probably passing the wrong value for structname.
899        members = info.getMembers()
900        assert(members)
901
902        # If this fails, see caller: this should only get called for a struct type with a type value.
903        param = findNamedElem(members, self.structtype_member_name)
904        # OpenXR gets some structs without a type field in here, so cannot assert
905        assert(param is not None)
906        # if param is None:
907        #     return None
908
909        entry = ValidityEntry(
910            anchor=(self.structtype_member_name, self.structtype_member_name))
911        entry += self.makeParameterName(self.structtype_member_name)
912        entry += ' must: be '
913
914        values = param.get('values', '').split(',')
915        if values:
916            # Extract each enumerant value. They could be validated in the
917            # same fashion as validextensionstructs in
918            # makeStructureExtensionPointer, although that is not relevant in
919            # the current extension struct model.
920            entry += self.makeProseList((self.makeEnumerantName(v)
921                                         for v in values), 'or')
922            return entry
923
924        if 'Base' in structname:
925            # This type does not even have any values for its type, and it
926            # seems like it might be a base struct that we would expect to
927            # lack its own type, so omit the entire statement
928            return None
929
930        self.logMsg('warn', 'No values were marked-up for the structure type member of',
931                    structname, 'so making one up!')
932        entry += self.makeStructureTypeFromName(structname)
933
934        return entry
935
936    def makeStructureExtensionPointer(self, blockname, param):
937        """Generate an validity line for the pointer chain member value of a struct."""
938        param_name = getElemName(param)
939
940        if param.get('validextensionstructs') is not None:
941            self.logMsg('warn', blockname,
942                        'validextensionstructs is deprecated/removed', '\n')
943
944        entry = ValidityEntry(
945            anchor=(param_name, self.nextpointer_member_name))
946        validextensionstructs = self.registry.validextensionstructs.get(
947            blockname)
948        extensionstructs = []
949        duplicatestructs = []
950
951        if validextensionstructs is not None:
952            # Check each structure name and skip it if not required by the
953            # generator. This allows tagging extension structs in the XML
954            # that are only included in validity when needed for the spec
955            # being targeted.
956            # Track the required structures, and of the required structures,
957            # those that allow duplicates in the pNext chain.
958            for struct in validextensionstructs:
959                # Unpleasantly breaks encapsulation. Should be a method in the registry class
960                t = self.registry.lookupElementInfo(
961                    struct, self.registry.typedict)
962                if t is None:
963                    self.logMsg('warn', 'makeStructureExtensionPointer: struct', struct,
964                                'is in a validextensionstructs= attribute but is not in the registry')
965                elif t.required:
966                    extensionstructs.append('slink:' + struct)
967                    if t.elem.get('allowduplicate') == 'true':
968                        duplicatestructs.append('slink:' + struct)
969                else:
970                    self.logMsg(
971                        'diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required')
972
973        if not extensionstructs:
974            entry += '{} must: be {}'.format(
975                self.makeParameterName(param_name), self.null)
976            return entry
977
978        if len(extensionstructs) == 1:
979            entry += '{} must: be {} or a pointer to a valid instance of {}'.format(self.makeParameterName(param_name), self.null,
980                                                                                    extensionstructs[0])
981        else:
982            # More than one extension struct.
983            entry += 'Each {} member of any structure (including this one) in the pname:{} chain '.format(
984                self.makeParameterName(param_name), self.nextpointer_member_name)
985            entry += 'must: be either {} or a pointer to a valid instance of '.format(
986                self.null)
987
988            entry += self.makeProseList(extensionstructs, fmt=plf.OR)
989
990        validity = self.makeValidityCollection(blockname)
991        validity += entry
992
993        # Generate VU statement requiring unique structures in the pNext
994        # chain.
995        # NOTE: OpenXR always allows non-unique type values. Instances other
996        # than the first are just ignored
997
998        vu = ('The pname:' +
999              self.structtype_member_name +
1000              ' value of each struct in the pname:' +
1001              self.nextpointer_member_name +
1002              ' chain must: be unique')
1003        anchor = (self.conventions.member_used_for_unique_vuid, 'unique')
1004
1005        # If duplicates of some structures are allowed, they are called out
1006        # explicitly.
1007        num = len(duplicatestructs)
1008        if num > 0:
1009            vu = (vu +
1010                  ', with the exception of structures of type ' +
1011                  self.makeProseList(duplicatestructs, fmt=plf.OR))
1012
1013        validity.addValidityEntry(vu, anchor = anchor )
1014
1015        return validity
1016
1017    def addSharedStructMemberValidity(self, struct, blockname, param, validity):
1018        """Generate language to independently validate a parameter, for those validated even in output.
1019
1020        Return value indicates whether it was handled internally (True) or if it may need more validity (False)."""
1021        param_name = getElemName(param)
1022        paramtype = getElemType(param)
1023        if param.get('noautovalidity') is None:
1024
1025            if self.conventions.is_structure_type_member(paramtype, param_name):
1026                validity += self.makeStructureTypeValidity(blockname)
1027                return True
1028
1029            if self.conventions.is_nextpointer_member(paramtype, param_name):
1030                # Vulkan: the addition of validity here is conditional unlike OpenXR.
1031                if struct.get('structextends') is None:
1032                    validity += self.makeStructureExtensionPointer(
1033                        blockname, param)
1034                return True
1035        return False
1036
1037    def makeOutputOnlyStructValidity(self, cmd, blockname, params):
1038        """Generate all the valid usage information for a struct that is entirely output.
1039
1040        That is, it is only ever filled out by the implementation other than
1041        the structure type and pointer chain members.
1042        Thus, we only create validity for the pointer chain member.
1043        """
1044        # Start the validity collection for this struct
1045        validity = self.makeValidityCollection(blockname)
1046
1047        for param in params:
1048            self.addSharedStructMemberValidity(
1049                cmd, blockname, param, validity)
1050
1051        return validity
1052
1053    def isVKVersion11(self):
1054        """Returns true if VK_VERSION_1_1 is being emitted."""
1055        vk11 = re.match(self.registry.genOpts.emitversions, 'VK_VERSION_1_1') is not None
1056        return vk11
1057
1058    def videocodingRequired(self):
1059        """Returns true if VK_KHR_video_queue is being emitted and thus validity
1060        with respect to the videocoding attribute should be generated."""
1061        return 'VK_KHR_video_queue' in self.registry.requiredextensions
1062
1063    def getVideocoding(self, cmd):
1064        """Returns the value of the videocoding attribute, also considering the
1065        default value when the attribute is not present."""
1066        videocoding = cmd.get('videocoding')
1067        if videocoding is None:
1068            videocoding = 'outside'
1069        return videocoding
1070
1071    def conditionallyRemoveQueueType(self, queues, queuetype, condition):
1072        """Removes a queue type from a queue list based on the specified condition."""
1073        if queuetype in queues and condition:
1074            queues.remove(queuetype)
1075
1076    def getQueueList(self, cmd):
1077        """Returns the list of queue types a command is supported on."""
1078        queues = cmd.get('queues')
1079        if queues is None:
1080            return None
1081        queues = queues.split(',')
1082
1083        # Filter queue types that have dependencies
1084        self.conditionallyRemoveQueueType(queues, 'sparse_binding', self.conventions.xml_api_name == "vulkansc")
1085        self.conditionallyRemoveQueueType(queues, 'decode',         'VK_KHR_video_decode_queue' not in self.registry.requiredextensions)
1086        self.conditionallyRemoveQueueType(queues, 'encode',         'VK_KHR_video_encode_queue' not in self.registry.requiredextensions)
1087        self.conditionallyRemoveQueueType(queues, 'opticalflow',    'VK_NV_optical_flow' not in self.registry.requiredextensions)
1088
1089        # Verify that no new queue type is introduced accidentally
1090        for queue in queues:
1091            if queue not in [ 'transfer', 'compute', 'graphics', 'sparse_binding', 'decode', 'encode', 'opticalflow' ]:
1092                self.logMsg('error', f'Unknown queue type "{queue}".')
1093
1094        return queues
1095
1096    def getPrettyQueueList(self, cmd):
1097        """Returns a prettified version of the queue list which can be included in spec language text."""
1098        queues = self.getQueueList(cmd)
1099        if queues is None:
1100            return None
1101
1102        replace = {
1103            'sparse_binding': 'sparse binding',
1104            'opticalflow': 'optical flow'
1105        }
1106        return [replace[queue] if queue in replace else queue for queue in queues]
1107
1108    def makeStructOrCommandValidity(self, cmd, blockname, params):
1109        """Generate all the valid usage information for a given struct or command."""
1110        validity = self.makeValidityCollection(blockname)
1111        handles = []
1112        arraylengths = dict()
1113        for param in params:
1114            param_name = getElemName(param)
1115            paramtype = getElemType(param)
1116
1117            # Valid usage ID tags (VUID) are generated for various
1118            # conditions based on the name of the block (structure or
1119            # command), name of the element (member or parameter), and type
1120            # of VU statement.
1121
1122            # Get the type's category
1123            typecategory = self.getTypeCategory(paramtype)
1124
1125            if not self.addSharedStructMemberValidity(
1126                    cmd, blockname, param, validity):
1127                if not param.get('selector'):
1128                    validity += self.createValidationLineForParameter(
1129                        blockname, param, params, typecategory, None, None)
1130                else:
1131                    selector = param.get('selector')
1132                    if typecategory != 'union':
1133                        self.logMsg('warn', 'selector attribute set on non-union parameter', param_name, 'in', blockname)
1134
1135                    paraminfo = self.registry.lookupElementInfo(paramtype, self.registry.typedict)
1136
1137                    for member in paraminfo.getMembers():
1138                        membertype = getElemType(member)
1139                        membertypecategory = self.getTypeCategory(membertype)
1140
1141                        validity += self.createValidationLineForParameter(
1142                            blockname, member, paraminfo.getMembers(), membertypecategory, selector, param_name)
1143
1144            # Ensure that any parenting is properly validated, and list that a handle was found
1145            if typecategory == 'handle':
1146                handles.append(param)
1147
1148            # Get the array length for this parameter
1149            lengths = LengthEntry.parse_len_from_param(param)
1150            if lengths:
1151                arraylengths.update({length.other_param_name: length
1152                                     for length in lengths
1153                                     if length.other_param_name})
1154
1155        # For any vkQueue* functions, there might be queue type data
1156        if 'vkQueue' in blockname:
1157            # The queue type must be valid
1158            queues = self.getPrettyQueueList(cmd)
1159            if queues:
1160                entry = ValidityEntry(anchor=('queuetype',))
1161                entry += 'The pname:queue must: support '
1162                entry += self.makeProseList(queues,
1163                                            fmt=plf.OR, comma_for_two_elts=True)
1164                entry += ' operations'
1165                validity += entry
1166
1167        if 'vkCmd' in blockname:
1168            # The commandBuffer parameter must be being recorded
1169            entry = ValidityEntry(anchor=('commandBuffer', 'recording'))
1170            entry += 'pname:commandBuffer must: be in the <<commandbuffers-lifecycle, recording state>>'
1171            validity += entry
1172
1173            #
1174            # Start of valid queue type validation - command pool must have been
1175            # allocated against a queue with at least one of the valid queue types
1176            entry = ValidityEntry(anchor=('commandBuffer', 'cmdpool'))
1177
1178            #
1179            # This test for vkCmdFillBuffer is a hack, since we have no path
1180            # to conditionally have queues enabled or disabled by an extension.
1181            # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
1182            if blockname == 'vkCmdFillBuffer':
1183                entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1184                if self.isVKVersion11() or 'VK_KHR_maintenance1' in self.registry.requiredextensions:
1185                    entry += 'transfer, graphics or compute operations'
1186                else:
1187                    entry += 'graphics or compute operations'
1188            else:
1189                # The queue type must be valid
1190                queues = self.getPrettyQueueList(cmd)
1191                assert(queues)
1192                entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1193                entry += self.makeProseList(queues,
1194                                            fmt=plf.OR, comma_for_two_elts=True)
1195                entry += ' operations'
1196            validity += entry
1197
1198            # Must be called inside/outside a render pass appropriately
1199            renderpass = cmd.get('renderpass')
1200
1201            if renderpass != 'both':
1202                entry = ValidityEntry(anchor=('renderpass',))
1203                entry += 'This command must: only be called '
1204                entry += renderpass
1205                entry += ' of a render pass instance'
1206                validity += entry
1207
1208            # Must be called inside/outside a video coding scope appropriately
1209            if self.videocodingRequired():
1210                videocoding = self.getVideocoding(cmd)
1211                if videocoding != 'both':
1212                    entry = ValidityEntry(anchor=('videocoding',))
1213                    entry += 'This command must: only be called '
1214                    entry += videocoding
1215                    entry += ' of a video coding scope'
1216                    validity += entry
1217
1218            # Must be in the right level command buffer
1219            cmdbufferlevel = cmd.get('cmdbufferlevel')
1220
1221            if cmdbufferlevel != 'primary,secondary':
1222                entry = ValidityEntry(anchor=('bufferlevel',))
1223                entry += 'pname:commandBuffer must: be a '
1224                entry += cmdbufferlevel
1225                entry += ' sname:VkCommandBuffer'
1226                validity += entry
1227
1228        # Any non-optional arraylengths should specify they must be greater than 0
1229        array_length_params = ((param, getElemName(param))
1230                               for param in params
1231                               if getElemName(param) in arraylengths)
1232
1233        for param, param_name in array_length_params:
1234            if param.get('optional') is not None:
1235                continue
1236
1237            length = arraylengths[param_name]
1238            full_length = length.full_reference
1239
1240            # Is this just a name of a param? If false, then it is some kind
1241            # of qualified name (a member of a param for instance)
1242            simple_param_reference = (len(length.param_ref_parts) == 1)
1243            if not simple_param_reference:
1244                # Loop through to see if any parameters in the chain are optional
1245                array_length_parent = cmd
1246                array_length_optional = False
1247                for part in length.param_ref_parts:
1248                    # Overwrite the param so it ends up as the bottom level parameter for later checks
1249                    param = array_length_parent.find("*/[name='{}']".format(part))
1250
1251                    # If any parameter in the chain is optional, skip the implicit length requirement
1252                    array_length_optional |= (param.get('optional') is not None)
1253
1254                    # Lookup the type of the parameter for the next loop iteration
1255                    type = param.findtext('type')
1256                    array_length_parent = self.registry.tree.find("./types/type/[@name='{}']".format(type))
1257
1258                if array_length_optional:
1259                    continue
1260
1261            # Get all the array dependencies
1262            arrays = cmd.findall(
1263                "param/[@len='{}'][@optional='true']".format(full_length))
1264
1265            # Get all the optional array dependencies, including those not generating validity for some reason
1266            optionalarrays = arrays + \
1267                cmd.findall(
1268                    "param/[@len='{}'][@noautovalidity='true']".format(full_length))
1269
1270            entry = ValidityEntry(anchor=(full_length, 'arraylength'))
1271            # Allow lengths to be arbitrary if all their dependents are optional
1272            if optionalarrays and len(optionalarrays) == len(arrays):
1273                entry += 'If '
1274                # TODO sync this section from OpenXR once cosmetic changes OK
1275
1276                optional_array_names = (self.makeParameterName(getElemName(array))
1277                                        for array in optionalarrays)
1278                entry += self.makeProseListIs(optional_array_names,
1279                                              plf.ANY_OR, comma_for_two_elts=True)
1280
1281                entry += ' not {}, '.format(self.null)
1282
1283            # TODO end needs sync cosmetic
1284            if self.paramIsPointer(param):
1285                entry += 'the value referenced by '
1286
1287            # Split and re-join here to insert pname: around ::
1288            entry += '::'.join(self.makeParameterName(part)
1289                               for part in full_length.split('::'))
1290            # TODO replace the previous statement with the following when cosmetic changes OK
1291            # entry += length.get_human_readable(make_param_name=self.makeParameterName)
1292
1293            entry += ' must: be greater than '
1294            entry += self.conventions.zero
1295            validity += entry
1296
1297        # Find the parents of all objects referenced in this command
1298        for param in handles:
1299            # Do not detect a parent for return values!
1300            if not self.paramIsPointer(param) or self.paramIsConst(param):
1301                validity += self.makeHandleValidityParent(param, params)
1302
1303        # Find the common ancestor of all objects referenced in this command
1304        validity += self.makeAsciiDocHandlesCommonAncestor(
1305            blockname, handles, params)
1306
1307        return validity
1308
1309    def makeThreadSafetyBlock(self, cmd, paramtext):
1310        """Generate thread-safety validity entries for cmd/structure"""
1311        # See also makeThreadSafetyBlock in validitygenerator.py
1312        validity = self.makeValidityCollection(getElemName(cmd))
1313
1314        # This text varies between projects, so an Asciidoctor attribute is used.
1315        extsync_prefix = "{externsyncprefix} "
1316
1317        # Find and add any parameters that are thread unsafe
1318        explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
1319        if explicitexternsyncparams is not None:
1320            for param in explicitexternsyncparams:
1321                externsyncattribs = ExternSyncEntry.parse_externsync_from_param(
1322                    param)
1323                param_name = getElemName(param)
1324
1325                for attrib in externsyncattribs:
1326                    entry = ValidityEntry()
1327                    entry += extsync_prefix
1328                    if attrib.entirely_extern_sync:
1329                        if self.paramIsArray(param):
1330                            entry += 'each member of '
1331                        elif self.paramIsPointer(param):
1332                            entry += 'the object referenced by '
1333
1334                        entry += self.makeParameterName(param_name)
1335
1336                        if attrib.children_extern_sync:
1337                            entry += ', and any child handles,'
1338
1339                    else:
1340                        entry += 'pname:'
1341                        entry += str(attrib.full_reference)
1342                        # TODO switch to the following when cosmetic changes OK
1343                        # entry += attrib.get_human_readable(make_param_name=self.makeParameterName)
1344                    entry += ' must: be externally synchronized'
1345                    validity += entry
1346
1347        # Vulkan-specific
1348        # For any vkCmd* functions, the command pool is externally synchronized
1349        if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name').text:
1350            entry = ValidityEntry()
1351            entry += extsync_prefix
1352            entry += 'the sname:VkCommandPool that pname:commandBuffer was allocated from must: be externally synchronized'
1353            validity += entry
1354
1355        # Find and add any "implicit" parameters that are thread unsafe
1356        implicitexternsyncparams = cmd.find('implicitexternsyncparams')
1357        if implicitexternsyncparams is not None:
1358            for elem in implicitexternsyncparams:
1359                entry = ValidityEntry()
1360                entry += extsync_prefix
1361                entry += elem.text
1362                entry += ' must: be externally synchronized'
1363                validity += entry
1364
1365        return validity
1366
1367    def makeCommandPropertiesTableHeader(self):
1368        header  = '|<<VkCommandBufferLevel,Command Buffer Levels>>'
1369        header += '|<<vkCmdBeginRenderPass,Render Pass Scope>>'
1370        if self.videocodingRequired():
1371            header += '|<<vkCmdBeginVideoCodingKHR,Video Coding Scope>>'
1372        header += '|<<VkQueueFlagBits,Supported Queue Types>>'
1373        header += '|<<fundamentals-queueoperation-command-types,Command Type>>'
1374        return header
1375
1376    def makeCommandPropertiesTableEntry(self, cmd, name):
1377        cmdbufferlevel, renderpass, videocoding, queues, tasks = None, None, None, None, None
1378
1379        if 'vkCmd' in name:
1380            # Must be called in primary/secondary command buffers appropriately
1381            cmdbufferlevel = cmd.get('cmdbufferlevel')
1382            cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(','))
1383
1384            # Must be called inside/outside a render pass appropriately
1385            renderpass = cmd.get('renderpass')
1386            renderpass = renderpass.capitalize()
1387
1388            # Must be called inside/outside a video coding scope appropriately
1389            if self.videocodingRequired():
1390                videocoding = self.getVideocoding(cmd).capitalize()
1391
1392            #
1393            # This test for vkCmdFillBuffer is a hack, since we have no path
1394            # to conditionally have queues enabled or disabled by an extension.
1395            # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
1396            if name == 'vkCmdFillBuffer':
1397                if self.isVKVersion11() or 'VK_KHR_maintenance1' in self.registry.requiredextensions:
1398                    queues = [ 'transfer', 'graphics', 'compute' ]
1399                else:
1400                    queues = [ 'graphics', 'compute' ]
1401            else:
1402                queues = self.getQueueList(cmd)
1403            queues = (' + \n').join([queue.title() for queue in queues])
1404
1405            tasks = cmd.get('tasks')
1406            tasks = (' + \n').join(tasks.title().split(','))
1407        elif 'vkQueue' in name:
1408            # For queue commands there are no command buffer level, render
1409            # pass, or video coding scope specific restrictions,
1410            # or command type, but the queue types are considered
1411            cmdbufferlevel = '-'
1412            renderpass = '-'
1413            if self.videocodingRequired():
1414                videocoding = '-'
1415
1416            queues = self.getQueueList(cmd)
1417            if queues is None:
1418                queues = 'Any'
1419            else:
1420                queues = (' + \n').join([queue.upper() for queue in queues])
1421
1422            tasks = '-'
1423
1424        table_items = (cmdbufferlevel, renderpass, videocoding, queues, tasks)
1425        entry = '|'.join(filter(None, table_items))
1426
1427        return ('|' + entry) if entry else None
1428
1429
1430    def findRequiredEnums(self, enums):
1431        """Check each enumerant name in the enums list and remove it if not
1432        required by the generator. This allows specifying success and error
1433        codes for extensions that are only included in validity when needed
1434        for the spec being targeted."""
1435        return self.keepOnlyRequired(enums, self.registry.enumdict)
1436
1437    def findRequiredCommands(self, commands):
1438        """Check each command name in the commands list and remove it if not
1439        required by the generator.
1440
1441        This will allow some state operations to take place before endFile."""
1442        return self.keepOnlyRequired(commands, self.registry.cmddict)
1443
1444    def keepOnlyRequired(self, names, info_dict):
1445        """Check each element name in the supplied dictionary and remove it if not
1446        required by the generator.
1447
1448        This will allow some operations to take place before endFile no matter the order of generation."""
1449        # TODO Unpleasantly breaks encapsulation. Should be a method in the registry class
1450
1451        def is_required(name):
1452            info = self.registry.lookupElementInfo(name, info_dict)
1453            if info is None:
1454                return False
1455            if not info.required:
1456                self.logMsg('diag', 'keepOnlyRequired: element',
1457                            name, 'IS NOT required, skipping')
1458            return info.required
1459
1460        return [name
1461                for name in names
1462                if is_required(name)]
1463
1464    def makeReturnCodeList(self, attrib, cmd, name):
1465        """Return a list of possible return codes for a function.
1466
1467        attrib is either 'successcodes' or 'errorcodes'.
1468        """
1469        return_lines = []
1470        RETURN_CODE_FORMAT = '* ename:{}'
1471
1472        codes_attr = cmd.get(attrib)
1473        if codes_attr:
1474            codes = self.findRequiredEnums(codes_attr.split(','))
1475            if codes:
1476                return_lines.extend((RETURN_CODE_FORMAT.format(code)
1477                                     for code in codes))
1478
1479        applicable_ext_codes = (ext_code
1480                                for ext_code in self.registry.commandextensionsuccesses
1481                                if ext_code.command == name)
1482        for ext_code in applicable_ext_codes:
1483            line = RETURN_CODE_FORMAT.format(ext_code.value)
1484            if ext_code.extension:
1485                line += ' [only if {} is enabled]'.format(
1486                    self.conventions.formatExtension(ext_code.extension))
1487
1488            return_lines.append(line)
1489        if return_lines:
1490            return '\n'.join(return_lines)
1491
1492        return None
1493
1494    def makeSuccessCodes(self, cmd, name):
1495        return self.makeReturnCodeList('successcodes', cmd, name)
1496
1497    def makeErrorCodes(self, cmd, name):
1498        return self.makeReturnCodeList('errorcodes', cmd, name)
1499
1500    def genCmd(self, cmdinfo, name, alias):
1501        """Command generation."""
1502        OutputGenerator.genCmd(self, cmdinfo, name, alias)
1503
1504        # @@@ (Jon) something needs to be done here to handle aliases, probably
1505
1506        validity = self.makeValidityCollection(name)
1507
1508        # OpenXR-only: make sure extension is enabled
1509        # validity.possiblyAddExtensionRequirement(self.currentExtension, 'calling flink:')
1510
1511        validity += self.makeStructOrCommandValidity(
1512            cmdinfo.elem, name, cmdinfo.getParams())
1513
1514        threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param')
1515        commandpropertiesentry = None
1516
1517        # Vulkan-specific
1518        commandpropertiesentry = self.makeCommandPropertiesTableEntry(
1519            cmdinfo.elem, name)
1520        successcodes = self.makeSuccessCodes(cmdinfo.elem, name)
1521        errorcodes = self.makeErrorCodes(cmdinfo.elem, name)
1522
1523        # OpenXR-specific
1524        # self.generateStateValidity(validity, name)
1525
1526        self.writeInclude('protos', name, validity, threadsafety,
1527                          commandpropertiesentry, successcodes, errorcodes)
1528
1529    def genStruct(self, typeinfo, typeName, alias):
1530        """Struct Generation."""
1531        OutputGenerator.genStruct(self, typeinfo, typeName, alias)
1532
1533        # @@@ (Jon) something needs to be done here to handle aliases, probably
1534
1535        # Anything that is only ever returned cannot be set by the user, so
1536        # should not have any validity information.
1537        validity = self.makeValidityCollection(typeName)
1538        threadsafety = []
1539
1540        # OpenXR-only: make sure extension is enabled
1541        # validity.possiblyAddExtensionRequirement(self.currentExtension, 'using slink:')
1542
1543        if typeinfo.elem.get('category') != 'union':
1544            if typeinfo.elem.get('returnedonly') is None:
1545                validity += self.makeStructOrCommandValidity(
1546                    typeinfo.elem, typeName, typeinfo.getMembers())
1547                threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member')
1548
1549            else:
1550                # Need to generate structure type and next pointer chain member validation
1551                validity += self.makeOutputOnlyStructValidity(
1552                    typeinfo.elem, typeName, typeinfo.getMembers())
1553
1554        self.writeInclude('structs', typeName, validity,
1555                          threadsafety, None, None, None)
1556
1557    def genGroup(self, groupinfo, groupName, alias):
1558        """Group (e.g. C "enum" type) generation.
1559        For the validity generator, this just tags individual enumerants
1560        as required or not.
1561        """
1562        OutputGenerator.genGroup(self, groupinfo, groupName, alias)
1563
1564        # @@@ (Jon) something needs to be done here to handle aliases, probably
1565
1566        groupElem = groupinfo.elem
1567
1568        # Loop over the nested 'enum' tags. Keep track of the minimum and
1569        # maximum numeric values, if they can be determined; but only for
1570        # core API enumerants, not extension enumerants. This is inferred
1571        # by looking for 'extends' attributes.
1572        for elem in groupElem.findall('enum'):
1573            name = elem.get('name')
1574            ei = self.registry.lookupElementInfo(name, self.registry.enumdict)
1575
1576            if ei is None:
1577                self.logMsg('error',
1578                    f'genGroup({groupName}) - no element found for enum {name}')
1579
1580            # Tag enumerant as required or not
1581            ei.required = self.isEnumRequired(elem)
1582
1583    def genType(self, typeinfo, name, alias):
1584        """Type Generation."""
1585        OutputGenerator.genType(self, typeinfo, name, alias)
1586
1587        # @@@ (Jon) something needs to be done here to handle aliases, probably
1588
1589        category = typeinfo.elem.get('category')
1590        if category in ('struct', 'union'):
1591            self.genStruct(typeinfo, name, alias)
1592