• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2#
3# Copyright (c) 2019 Collabora, Ltd.
4#
5# SPDX-License-Identifier: Apache-2.0
6#
7# Author(s):    Ryan Pavlik <ryan.pavlik@collabora.com>
8#
9# Purpose:      This script checks some "business logic" in the XML registry.
10
11import re
12import sys
13from pathlib import Path
14from collections import defaultdict, deque, namedtuple
15
16from check_spec_links import VulkanEntityDatabase as OrigEntityDatabase
17from reg import Registry
18from spec_tools.consistency_tools import XMLChecker
19from spec_tools.util import findNamedElem, getElemName, getElemType
20from apiconventions import APIConventions
21
22# These are extensions which do not follow the usual naming conventions,
23# specifying the alternate convention they follow
24EXTENSION_ENUM_NAME_SPELLING_CHANGE = {
25    'VK_EXT_swapchain_colorspace': 'VK_EXT_SWAPCHAIN_COLOR_SPACE',
26}
27
28# These are extensions whose names *look* like they end in version numbers,
29# but do not
30EXTENSION_NAME_VERSION_EXCEPTIONS = (
31    'VK_AMD_gpu_shader_int16',
32    'VK_EXT_index_type_uint8',
33    'VK_EXT_shader_image_atomic_int64',
34    'VK_KHR_video_decode_h264',
35    'VK_KHR_video_decode_h265',
36    'VK_EXT_video_encode_h264',
37    'VK_EXT_video_encode_h265',
38    'VK_KHR_external_fence_win32',
39    'VK_KHR_external_memory_win32',
40    'VK_KHR_external_semaphore_win32',
41    'VK_KHR_shader_atomic_int64',
42    'VK_KHR_shader_float16_int8',
43    'VK_KHR_spirv_1_4',
44    'VK_NV_external_memory_win32',
45    'VK_RESERVED_do_not_use_146',
46    'VK_RESERVED_do_not_use_94',
47)
48
49# Exceptions to pointer parameter naming rules
50# Keyed by (entity name, type, name).
51CHECK_PARAM_POINTER_NAME_EXCEPTIONS = {
52    ('vkGetDrmDisplayEXT', 'VkDisplayKHR', 'display') : None,
53}
54
55# Exceptions to pNext member requiring an optional attribute
56CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS = (
57    'VkVideoEncodeInfoKHR',
58    'VkVideoEncodeRateControlLayerInfoKHR',
59)
60
61# Exceptions to VK_INCOMPLETE being required for, and only applicable to, array
62# enumeration functions
63CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS = (
64    'vkGetDeviceFaultInfoEXT',
65)
66
67def get_extension_commands(reg):
68    extension_cmds = set()
69    for ext in reg.extensions:
70        for cmd in ext.findall('./require/command[@name]'):
71            extension_cmds.add(cmd.get('name'))
72    return extension_cmds
73
74
75def get_enum_value_names(reg, enum_type):
76    names = set()
77    result_elem = reg.groupdict[enum_type].elem
78    for val in result_elem.findall('./enum[@name]'):
79        names.add(val.get('name'))
80    return names
81
82
83# Regular expression matching an extension name ending in a (possible) version number
84EXTNAME_RE = re.compile(r'(?P<base>(\w+[A-Za-z]))(?P<version>\d+)')
85
86DESTROY_PREFIX = 'vkDestroy'
87TYPEENUM = 'VkStructureType'
88
89SPECIFICATION_DIR = Path(__file__).parent.parent
90REVISION_RE = re.compile(r' *[*] Revision (?P<num>[1-9][0-9]*),.*')
91
92
93def get_extension_source(extname):
94    fn = f'{extname}.adoc'
95    return str(SPECIFICATION_DIR / 'appendices' / fn)
96
97
98class EntityDatabase(OrigEntityDatabase):
99
100    # Override base class method to not exclude 'disabled' extensions
101    def getExclusionSet(self):
102        """Return a set of "support=" attribute strings that should not be included in the database.
103
104        Called only during construction."""
105
106        return set(())
107
108    def makeRegistry(self):
109        try:
110            import lxml.etree as etree
111            HAS_LXML = True
112        except ImportError:
113            HAS_LXML = False
114        if not HAS_LXML:
115            return super().makeRegistry()
116
117        registryFile = str(SPECIFICATION_DIR / 'xml/vk.xml')
118        registry = Registry()
119        registry.filename = registryFile
120        registry.loadElementTree(etree.parse(registryFile))
121        return registry
122
123
124class Checker(XMLChecker):
125    def __init__(self):
126        manual_types_to_codes = {
127            # These are hard-coded "manual" return codes:
128            # the codes of the value (string, list, or tuple)
129            # are available for a command if-and-only-if
130            # the key type is passed as an input.
131            'VkFormat': 'VK_ERROR_FORMAT_NOT_SUPPORTED'
132        }
133        forward_only = {
134            # Like the above, but these are only valid in the
135            # "type implies return code" direction
136        }
137        reverse_only = {
138            # like the above, but these are only valid in the
139            # "return code implies type or its descendant" direction
140            # "XrDuration": "XR_TIMEOUT_EXPIRED"
141        }
142        # Some return codes are related in that only one of a set
143        # may be returned by a command
144        # (eg. XR_ERROR_SESSION_RUNNING and XR_ERROR_SESSION_NOT_RUNNING)
145        self.exclusive_return_code_sets = tuple(
146            # set(("XR_ERROR_SESSION_NOT_RUNNING", "XR_ERROR_SESSION_RUNNING")),
147        )
148
149        # This is used to report collisions.
150        conventions = APIConventions()
151        db = EntityDatabase()
152
153        self.extension_cmds = get_extension_commands(db.registry)
154        self.return_codes = get_enum_value_names(db.registry, 'VkResult')
155        self.structure_types = get_enum_value_names(db.registry, TYPEENUM)
156
157        # Dict of entity name to a list of messages to suppress. (Exclude any context data and "Warning:"/"Error:")
158        # Keys are entity names, values are tuples or lists of message text to suppress.
159        suppressions = {}
160
161        # Structures explicitly allowed to have 'limittype' attributes
162        self.allowedStructs = set((
163            'VkFormatProperties',
164            'VkFormatProperties2',
165            'VkPhysicalDeviceProperties',
166            'VkPhysicalDeviceProperties2',
167            'VkPhysicalDeviceLimits',
168            'VkQueueFamilyProperties',
169            'VkQueueFamilyProperties2',
170            'VkSparseImageFormatProperties',
171            'VkSparseImageFormatProperties2',
172        ))
173
174        # Substructures of allowed structures. This can be found by looking
175        # at tags, but there are so few cases that it is hardwired for now.
176        self.nestedStructs = set((
177            'VkPhysicalDeviceLimits',
178            'VkPhysicalDeviceSparseProperties',
179            'VkPhysicalDeviceProperties',
180            'VkQueueFamilyProperties',
181            'VkSparseImageFormatProperties',
182        ))
183
184        # Structures all of whose (non pNext/sType) members are required to
185        # have 'limittype' attributes, as are their descendants
186        self.requiredStructs = set((
187            'VkPhysicalDeviceProperties',
188            'VkPhysicalDeviceProperties2',
189            'VkPhysicalDeviceLimits',
190            'VkSparseImageFormatProperties',
191            'VkSparseImageFormatProperties2',
192        ))
193
194        # Structures which have already have their limittype attributes validated
195        self.validatedLimittype = set()
196
197        # Initialize superclass
198        super().__init__(entity_db=db, conventions=conventions,
199                         manual_types_to_codes=manual_types_to_codes,
200                         forward_only_types_to_codes=forward_only,
201                         reverse_only_types_to_codes=reverse_only,
202                         suppressions=suppressions)
203
204    def check(self):
205        """Extends base class behavior with additional checks"""
206
207        # This test is not run on a per-structure basis, but loops over
208        # specific structures
209        self.check_type_required_limittype()
210
211        super().check()
212
213    def check_command(self, name, info):
214        """Extends base class behavior with additional checks"""
215
216        if name[0:5] == 'vkCmd':
217            if info.elem.get('tasks') is None:
218                self.record_error(f'{name} is a vkCmd* command, but is missing a "tasks" attribute')
219
220        super().check_command(name, info)
221
222    def check_command_return_codes_basic(self, name, info,
223                                         successcodes, errorcodes):
224        """Check a command's return codes for consistency.
225
226        Called on every command."""
227        # Check that all extension commands can return the code associated
228        # with trying to use an extension that was not enabled.
229        # if name in self.extension_cmds and UNSUPPORTED not in errorcodes:
230        #     self.record_error('Missing expected return code',
231        #                       UNSUPPORTED,
232        #                       'implied due to being an extension command')
233
234        codes = successcodes.union(errorcodes)
235
236        # Check that all return codes are recognized.
237        unrecognized = codes - self.return_codes
238        if unrecognized:
239            self.record_error('Unrecognized return code(s):',
240                              unrecognized)
241
242        elem = info.elem
243        params = [(getElemName(elt), elt) for elt in elem.findall('param')]
244
245        def is_count_output(name, elt):
246            # Must end with Count or Size,
247            # not be const,
248            # and be a pointer (detected by naming convention)
249            return (name.endswith('Count') or name.endswith('Size')) \
250                and (elt.tail is None or 'const' not in elt.tail) \
251                and (name.startswith('p'))
252
253        countParams = [elt
254                       for name, elt in params
255                       if is_count_output(name, elt)]
256        if countParams:
257            assert(len(countParams) == 1)
258            if 'VK_INCOMPLETE' not in successcodes:
259                message = "Apparent enumeration of an array without VK_INCOMPLETE in successcodes for command {}.".format(name)
260                if name in CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS:
261                    self.record_warning('(Allowed exception)', message)
262                else:
263                    self.record_error(message)
264
265        elif 'VK_INCOMPLETE' in successcodes:
266            message = "VK_INCOMPLETE in successcodes of command {} that is apparently not an array enumeration.".format(name)
267            if name in CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS:
268                self.record_warning('(Allowed exception)', message)
269            else:
270                self.record_error(message)
271
272    def check_param(self, param):
273        """Check a member of a struct or a param of a function.
274
275        Called from check_params."""
276        super().check_param(param)
277
278        if not self.is_api_type(param):
279            return
280
281        param_text = ''.join(param.itertext())
282        param_name = getElemName(param)
283
284        # Make sure the number of leading 'p' matches the pointer count.
285        pointercount = param.find('type').tail
286        if pointercount:
287            pointercount = pointercount.count('*')
288        if pointercount:
289            prefix = 'p' * pointercount
290            if not param_name.startswith(prefix):
291                param_type = param.find('type').text
292                message = "Apparently incorrect pointer-related name prefix for {} - expected it to start with '{}'".format(
293                    param_text, prefix)
294                if (self.entity, param_type, param_name) in CHECK_PARAM_POINTER_NAME_EXCEPTIONS:
295                    self.record_warning('(Allowed exception)', message, elem=param)
296                else:
297                    self.record_error(message, elem=param)
298
299        # Make sure pNext members have optional="true" attributes
300        if param_name == self.conventions.nextpointer_member_name:
301            optional = param.get('optional')
302            if optional is None or optional != 'true':
303                message = f'{self.entity}.pNext member is missing \'optional="true"\' attribute'
304                if self.entity in CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS:
305                    self.record_warning('(Allowed exception)', message, elem=param)
306                else:
307                    self.record_error(message, elem=param)
308
309    def check_type_stype(self, name, info, type_elts):
310        """Check a struct type's sType member"""
311        if len(type_elts) > 1:
312            self.record_error(
313                'Have more than one member of type', TYPEENUM)
314        else:
315            type_elt = type_elts[0]
316            val = type_elt.get('values')
317            if val and val not in self.structure_types:
318                self.record_error('Unknown structure type constant', val)
319
320    def check_type_pnext(self, name, info):
321        """Check a struct type's pNext member, if present"""
322
323        next_name = self.conventions.nextpointer_member_name
324        next_member = findNamedElem(info.elem.findall('member'), next_name)
325        if next_member is not None:
326            # Ensure that the 'optional' attribute is set to 'true'
327            optional = next_member.get('optional')
328            if optional is None or optional != 'true':
329                message = f'{name}.{next_name} member is missing \'optional="true"\' attribute'
330                if name in CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS:
331                    self.record_warning('(Allowed exception)', message)
332                else:
333                    self.record_error(message)
334
335    def __isLimittypeStruct(self, name, info, allowedStructs):
336        """Check if a type element is a structure allowed to have 'limittype' attributes
337           name - name of a structure
338           info - corresponding TypeInfo object
339           allowedStructs - set of struct names explicitly allowed"""
340
341        # Is this an explicitly allowed struct?
342        if name in allowedStructs:
343            return True
344
345        # Is this a struct extending an explicitly allowed struct?
346        extends = info.elem.get('structextends')
347        if extends is not None:
348            # See if any name in the structextends attribute is an allowed
349            # struct
350            if len(set(extends.split(',')) & allowedStructs) > 0:
351                return True
352
353        return False
354
355    def __validateStructLimittypes(self, name, info, requiredLimittype):
356        """Validate 'limittype' attributes for a single struct.
357           info - TypeInfo for a struct <type>
358           requiredLimittype - True if members *must* have a limittype"""
359
360        # Do not re-check structures
361        if name in self.validatedLimittype:
362            return {}
363        self.validatedLimittype.add(name)
364
365        limittypeDiags = namedtuple('limittypeDiags', ['missing', 'invalid'])
366        badFields = defaultdict(lambda : limittypeDiags(missing=[], invalid=[]))
367        validLimittypes = { 'min', 'max', 'pot', 'mul', 'bits', 'bitmask', 'range', 'struct', 'exact', 'noauto' }
368        for member in info.getMembers():
369            memberName = member.findtext('name')
370            if memberName in ['sType', 'pNext']:
371                continue
372            limittype = member.get('limittype')
373            if limittype is None:
374                # Do not tag this as missing if it is not required
375                if requiredLimittype:
376                    badFields[info.elem.get('name')].missing.append(memberName)
377            elif limittype == 'struct':
378                typeName = member.findtext('type')
379                memberType = self.reg.typedict[typeName]
380                badFields.update(self.__validateStructLimittypes(typeName, memberType, requiredLimittype))
381            else:
382                for value in limittype.split(','):
383                    if value not in validLimittypes:
384                        badFields[info.elem.get('name')].invalid.append(memberName)
385
386        return badFields
387
388    def check_type_disallowed_limittype(self, name, info):
389        """Check if a struct type's members cannot have the 'limittype' attribute"""
390
391        # If not allowed to have limittypes, verify this for each member
392        if not self.__isLimittypeStruct(name, info, self.allowedStructs.union(self.nestedStructs)):
393            for member in info.getMembers():
394                if member.get('limittype') is not None:
395                    memname = member.findtext('name')
396                    self.record_error(f'{name} member {memname} has disallowed limittype attribute')
397
398    def check_type_required_limittype(self):
399        """Check struct type members which must have the 'limittype' attribute
400
401        Called from check."""
402
403        for name in self.allowedStructs:
404            # Assume that only extending structs of structs explicitly
405            # requiring limittypes also require them
406            requiredLimittype = (name in self.requiredStructs)
407
408            info = self.reg.typedict[name]
409
410            self.set_error_context(entity=name, elem=info.elem)
411
412            badFields = self.__validateStructLimittypes(name, info, requiredLimittype)
413            for extendingStructName in self.reg.validextensionstructs[name]:
414                extendingStruct = self.reg.typedict[extendingStructName]
415                badFields.update(self.__validateStructLimittypes(extendingStructName, extendingStruct, requiredLimittype))
416
417            if badFields:
418                for key in sorted(badFields.keys()):
419                    diags = badFields[key]
420                    if diags.missing:
421                        self.record_error(f'{name} missing limittype for members {", ".join(badFields[key].missing)}')
422                    if diags.invalid:
423                        self.record_error(f'{name} has invalid limittype for members {", ".join(badFields[key].invalid)}')
424
425    def check_type(self, name, info, category):
426        """Check a type's XML data for consistency.
427
428        Called from check."""
429
430        if category == 'struct':
431            type_elts = [elt
432                         for elt in info.elem.findall('member')
433                         if getElemType(elt) == TYPEENUM]
434
435            if type_elts:
436                self.check_type_stype(name, info, type_elts)
437                self.check_type_pnext(name, info)
438
439            # Check for disallowed limittypes on all structures
440            self.check_type_disallowed_limittype(name, info)
441        elif category == 'bitmask':
442            if 'Flags' in name:
443                expected_require = name.replace('Flags', 'FlagBits')
444                require = info.elem.get('require')
445                if require is not None and expected_require != require:
446                    self.record_error('Unexpected require attribute value:',
447                                      'got', require,
448                                      'but expected', expected_require)
449        super().check_type(name, info, category)
450
451    def check_extension(self, name, info, supported):
452        """Check an extension's XML data for consistency.
453
454        Called from check."""
455
456        elem = info.elem
457        enums = elem.findall('./require/enum[@name]')
458
459        # If extension name is not on the exception list and matches the
460        # versioned-extension pattern, map the extension name to the version
461        # name with the version as a separate word. Otherwise just map it to
462        # the upper-case version of the extension name.
463
464        matches = EXTNAME_RE.fullmatch(name)
465        ext_versioned_name = False
466        if name in EXTENSION_ENUM_NAME_SPELLING_CHANGE:
467            ext_enum_name = EXTENSION_ENUM_NAME_SPELLING_CHANGE.get(name)
468        elif matches is None or name in EXTENSION_NAME_VERSION_EXCEPTIONS:
469            # This is the usual case, either a name that does not look
470            # versioned, or one that does but is on the exception list.
471            ext_enum_name = name.upper()
472        else:
473            # This is a versioned extension name.
474            # Treat the version number as a separate word.
475            base = matches.group('base')
476            version = matches.group('version')
477            ext_enum_name = base.upper() + '_' + version
478            # Keep track of this case
479            ext_versioned_name = True
480
481        # Look for the expected SPEC_VERSION token name
482        version_name = f'{ext_enum_name}_SPEC_VERSION'
483        version_elem = findNamedElem(enums, version_name)
484
485        if version_elem is None:
486            # Did not find a SPEC_VERSION enum matching the extension name
487            if ext_versioned_name:
488                suffix = '\n\
489    Make sure that trailing version numbers in extension names are treated\n\
490    as separate words in extension enumerant names. If this is an extension\n\
491    whose name ends in a number which is not a version, such as "...h264"\n\
492    or "...int16", add it to EXTENSION_NAME_VERSION_EXCEPTIONS in\n\
493    scripts/xml_consistency.py.'
494            else:
495                suffix = ''
496            self.record_error(f'Missing version enum {version_name}{suffix}')
497        elif supported:
498            # Skip unsupported / disabled extensions for these checks
499
500            fn = get_extension_source(name)
501            revisions = []
502            with open(fn, 'r', encoding='utf-8') as fp:
503                for line in fp:
504                    line = line.rstrip()
505                    match = REVISION_RE.match(line)
506                    if match:
507                        revisions.append(int(match.group('num')))
508            ver_from_xml = version_elem.get('value')
509            if revisions:
510                ver_from_text = str(max(revisions))
511                if ver_from_xml != ver_from_text:
512                    self.record_error('Version enum mismatch: spec text indicates', ver_from_text,
513                                      'but XML says', ver_from_xml)
514            else:
515                if ver_from_xml == '1':
516                    self.record_warning(
517                        "Cannot find version history in spec text - make sure it has lines starting exactly like '* Revision 1, ....'",
518                        filename=fn)
519                else:
520                    self.record_warning("Cannot find version history in spec text, but XML reports a non-1 version number", ver_from_xml,
521                                        " - make sure the spec text has lines starting exactly like '* Revision 1, ....'",
522                                        filename=fn)
523
524        name_define = f'{ext_enum_name}_EXTENSION_NAME'
525        name_elem = findNamedElem(enums, name_define)
526        if name_elem is None:
527            self.record_error('Missing name enum', name_define)
528        else:
529            # Note: etree handles the XML entities here and turns &quot; back into "
530            expected_name = f'"{name}"'
531            name_val = name_elem.get('value')
532            if name_val != expected_name:
533                self.record_error('Incorrect name enum: expected', expected_name,
534                                  'got', name_val)
535
536        super().check_extension(name, info, supported)
537
538    def check_format(self):
539        """Check an extension's XML data for consistency.
540
541        Called from check."""
542
543        astc3d_formats = [
544                'VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT',
545                'VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT',
546                'VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT',
547                'VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT',
548                'VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT',
549                'VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT',
550                'VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT',
551                'VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT',
552                'VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT',
553                'VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT',
554                'VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT',
555                'VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT',
556                'VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT',
557                'VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT',
558                'VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT',
559                'VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT',
560                'VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT',
561                'VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT',
562                'VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT',
563                'VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT',
564                'VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT',
565                'VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT',
566                'VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT',
567                'VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT',
568                'VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT',
569                'VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT',
570                'VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT',
571                'VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT',
572                'VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT',
573                'VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT'
574        ]
575
576        # Need to build list of formats from rest of <enums>
577        enum_formats = []
578        for enum in self.reg.groupdict['VkFormat'].elem:
579            if enum.get('alias') is None and enum.get('name') != 'VK_FORMAT_UNDEFINED':
580                enum_formats.append(enum.get('name'))
581
582        found_formats = []
583        for name, info in self.reg.formatsdict.items():
584            found_formats.append(name)
585            self.set_error_context(entity=name, elem=info.elem)
586
587            if name not in enum_formats:
588                self.record_error('The <format> has no matching <enum> for', name)
589
590            # Check never just 1 plane
591            plane_elems = info.elem.findall('plane')
592            if len(plane_elems) == 1:
593                self.record_error('The <format> has only 1 <plane> for', name)
594
595            valid_chroma = ['420', '422', '444']
596            if info.elem.get('chroma') and info.elem.get('chroma') not in valid_chroma:
597                self.record_error('The <format> has chroma is not a valid value for', name)
598
599            # The formatsgenerator.py assumes only 1 <spirvimageformat> tag.
600            # If this changes in the future, remove this warning and update generator script
601            spirv_image_format = info.elem.findall('spirvimageformat')
602            if len(spirv_image_format) > 1:
603                self.record_error('More than 1 <spirvimageformat> but formatsgenerator.py is not updated, for format', name)
604
605        # Re-loop to check the other way if the <format> is missing
606        for enum in self.reg.groupdict['VkFormat'].elem:
607            name = enum.get('name')
608            if enum.get('alias') is None and name != 'VK_FORMAT_UNDEFINED':
609                if name not in found_formats and name not in astc3d_formats:
610                    self.set_error_context(entity=name, elem=enum)
611                    self.record_error('The <enum> has no matching <format> for ', name)
612
613        super().check_format()
614
615
616if __name__ == '__main__':
617
618    ckr = Checker()
619    ckr.check()
620
621    if ckr.fail:
622        sys.exit(1)
623