1e5c31af7Sopenharmony_ci#!/usr/bin/python3
2e5c31af7Sopenharmony_ci#
3e5c31af7Sopenharmony_ci# Copyright (c) 2019 Collabora, Ltd.
4e5c31af7Sopenharmony_ci#
5e5c31af7Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0
6e5c31af7Sopenharmony_ci#
7e5c31af7Sopenharmony_ci# Author(s):    Ryan Pavlik <ryan.pavlik@collabora.com>
8e5c31af7Sopenharmony_ci#
9e5c31af7Sopenharmony_ci# Purpose:      This script checks some "business logic" in the XML registry.
10e5c31af7Sopenharmony_ci
11e5c31af7Sopenharmony_ciimport argparse
12e5c31af7Sopenharmony_ciimport re
13e5c31af7Sopenharmony_ciimport sys
14e5c31af7Sopenharmony_cifrom pathlib import Path
15e5c31af7Sopenharmony_cifrom collections import defaultdict, deque, namedtuple
16e5c31af7Sopenharmony_ci
17e5c31af7Sopenharmony_cifrom check_spec_links import VulkanEntityDatabase as OrigEntityDatabase
18e5c31af7Sopenharmony_cifrom reg import Registry
19e5c31af7Sopenharmony_cifrom spec_tools.consistency_tools import XMLChecker
20e5c31af7Sopenharmony_cifrom spec_tools.util import findNamedElem, getElemName, getElemType
21e5c31af7Sopenharmony_cifrom apiconventions import APIConventions
22e5c31af7Sopenharmony_cifrom parse_dependency import dependencyNames
23e5c31af7Sopenharmony_ci
24e5c31af7Sopenharmony_ci# These are extensions which do not follow the usual naming conventions,
25e5c31af7Sopenharmony_ci# specifying the alternate convention they follow
26e5c31af7Sopenharmony_ciEXTENSION_ENUM_NAME_SPELLING_CHANGE = {
27e5c31af7Sopenharmony_ci    'VK_EXT_swapchain_colorspace': 'VK_EXT_SWAPCHAIN_COLOR_SPACE',
28e5c31af7Sopenharmony_ci}
29e5c31af7Sopenharmony_ci
30e5c31af7Sopenharmony_ci# These are extensions whose names *look* like they end in version numbers,
31e5c31af7Sopenharmony_ci# but do not
32e5c31af7Sopenharmony_ciEXTENSION_NAME_VERSION_EXCEPTIONS = (
33e5c31af7Sopenharmony_ci    'VK_AMD_gpu_shader_int16',
34e5c31af7Sopenharmony_ci    'VK_EXT_index_type_uint8',
35e5c31af7Sopenharmony_ci    'VK_EXT_shader_image_atomic_int64',
36e5c31af7Sopenharmony_ci    'VK_KHR_video_decode_h264',
37e5c31af7Sopenharmony_ci    'VK_KHR_video_decode_h265',
38e5c31af7Sopenharmony_ci    'VK_KHR_video_encode_h264',
39e5c31af7Sopenharmony_ci    'VK_KHR_video_encode_h265',
40e5c31af7Sopenharmony_ci    'VK_KHR_external_fence_win32',
41e5c31af7Sopenharmony_ci    'VK_KHR_external_memory_win32',
42e5c31af7Sopenharmony_ci    'VK_KHR_external_semaphore_win32',
43e5c31af7Sopenharmony_ci    'VK_KHR_shader_atomic_int64',
44e5c31af7Sopenharmony_ci    'VK_KHR_shader_float16_int8',
45e5c31af7Sopenharmony_ci    'VK_KHR_spirv_1_4',
46e5c31af7Sopenharmony_ci    'VK_NV_external_memory_win32',
47e5c31af7Sopenharmony_ci    'VK_RESERVED_do_not_use_146',
48e5c31af7Sopenharmony_ci    'VK_RESERVED_do_not_use_94',
49e5c31af7Sopenharmony_ci)
50e5c31af7Sopenharmony_ci
51e5c31af7Sopenharmony_ci# These are APIs which can be required by an extension despite not having
52e5c31af7Sopenharmony_ci# suffixes matching the vendor ID of that extension.
53e5c31af7Sopenharmony_ci# Most are external types.
54e5c31af7Sopenharmony_ci# We could make this an (extension name, api name) set to be more specific.
55e5c31af7Sopenharmony_ciEXTENSION_API_NAME_EXCEPTIONS = {
56e5c31af7Sopenharmony_ci    'AHardwareBuffer',
57e5c31af7Sopenharmony_ci    'ANativeWindow',
58e5c31af7Sopenharmony_ci    'CAMetalLayer',
59e5c31af7Sopenharmony_ci    'IOSurfaceRef',
60e5c31af7Sopenharmony_ci    'MTLBuffer_id',
61e5c31af7Sopenharmony_ci    'MTLCommandQueue_id',
62e5c31af7Sopenharmony_ci    'MTLDevice_id',
63e5c31af7Sopenharmony_ci    'MTLSharedEvent_id',
64e5c31af7Sopenharmony_ci    'MTLTexture_id',
65e5c31af7Sopenharmony_ci    'VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE',
66e5c31af7Sopenharmony_ci    'VkFlags64',
67e5c31af7Sopenharmony_ci    'VkPipelineCacheCreateFlagBits',
68e5c31af7Sopenharmony_ci    'VkPipelineColorBlendStateCreateFlagBits',
69e5c31af7Sopenharmony_ci    'VkPipelineDepthStencilStateCreateFlagBits',
70e5c31af7Sopenharmony_ci    'VkPipelineLayoutCreateFlagBits',
71e5c31af7Sopenharmony_ci}
72e5c31af7Sopenharmony_ci
73e5c31af7Sopenharmony_ci# These are APIs which contain _RESERVED_ intentionally
74e5c31af7Sopenharmony_ciEXTENSION_NAME_RESERVED_EXCEPTIONS = {
75e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_PRIVATE_VENDOR_INFO_RESERVED_OFFSET_0_NV'
76e5c31af7Sopenharmony_ci}
77e5c31af7Sopenharmony_ci
78e5c31af7Sopenharmony_ci# Exceptions to pointer parameter naming rules
79e5c31af7Sopenharmony_ci# Keyed by (entity name, type, name).
80e5c31af7Sopenharmony_ciCHECK_PARAM_POINTER_NAME_EXCEPTIONS = {
81e5c31af7Sopenharmony_ci    ('vkGetDrmDisplayEXT', 'VkDisplayKHR', 'display') : None,
82e5c31af7Sopenharmony_ci}
83e5c31af7Sopenharmony_ci
84e5c31af7Sopenharmony_ci# Exceptions to pNext member requiring an optional attribute
85e5c31af7Sopenharmony_ciCHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS = (
86e5c31af7Sopenharmony_ci    'VkVideoEncodeInfoKHR',
87e5c31af7Sopenharmony_ci    'VkVideoEncodeRateControlLayerInfoKHR',
88e5c31af7Sopenharmony_ci)
89e5c31af7Sopenharmony_ci
90e5c31af7Sopenharmony_ci# Exceptions to VK_INCOMPLETE being required for, and only applicable to, array
91e5c31af7Sopenharmony_ci# enumeration functions
92e5c31af7Sopenharmony_ciCHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS = (
93e5c31af7Sopenharmony_ci    'vkGetDeviceFaultInfoEXT',
94e5c31af7Sopenharmony_ci    'vkEnumerateDeviceLayerProperties',
95e5c31af7Sopenharmony_ci)
96e5c31af7Sopenharmony_ci
97e5c31af7Sopenharmony_ci# Exceptions to unknown structure type constants.
98e5c31af7Sopenharmony_ci# This is most likely an error in this script, not the XML.
99e5c31af7Sopenharmony_ci# It does not understand Vulkan SC (alternate 'api') types.
100e5c31af7Sopenharmony_ciCHECK_TYPE_STYPE_EXCEPTIONS = (
101e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_PERFORMANCE_QUERY_RESERVATION_INFO_KHR',
102e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_PIPELINE_POOL_SIZE',
103e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_FAULT_DATA',
104e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_SC_1_0_FEATURES',
105e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_DEVICE_OBJECT_RESERVATION_CREATE_INFO',
106e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_PIPELINE_OFFLINE_CREATE_INFO',
107e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_FAULT_CALLBACK_INFO',
108e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_COMMAND_POOL_MEMORY_RESERVATION_CREATE_INFO',
109e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_DEVICE_SEMAPHORE_SCI_SYNC_POOL_RESERVATION_CREATE_INFO_NV',
110e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_COMMAND_POOL_MEMORY_CONSUMPTION',
111e5c31af7Sopenharmony_ci    'VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_SC_1_0_PROPERTIES',
112e5c31af7Sopenharmony_ci)
113e5c31af7Sopenharmony_ci
114e5c31af7Sopenharmony_cidef get_extension_commands(reg):
115e5c31af7Sopenharmony_ci    extension_cmds = set()
116e5c31af7Sopenharmony_ci    for ext in reg.extensions:
117e5c31af7Sopenharmony_ci        for cmd in ext.findall('./require/command[@name]'):
118e5c31af7Sopenharmony_ci            extension_cmds.add(cmd.get('name'))
119e5c31af7Sopenharmony_ci    return extension_cmds
120e5c31af7Sopenharmony_ci
121e5c31af7Sopenharmony_ci
122e5c31af7Sopenharmony_cidef get_enum_value_names(reg, enum_type):
123e5c31af7Sopenharmony_ci    names = set()
124e5c31af7Sopenharmony_ci    result_elem = reg.groupdict[enum_type].elem
125e5c31af7Sopenharmony_ci    for val in result_elem.findall('./enum[@name]'):
126e5c31af7Sopenharmony_ci        names.add(val.get('name'))
127e5c31af7Sopenharmony_ci    return names
128e5c31af7Sopenharmony_ci
129e5c31af7Sopenharmony_ci
130e5c31af7Sopenharmony_ci# Regular expression matching an extension name ending in a (possible) version number
131e5c31af7Sopenharmony_ciEXTNAME_RE = re.compile(r'(?P<base>(\w+[A-Za-z]))(?P<version>\d+)')
132e5c31af7Sopenharmony_ci
133e5c31af7Sopenharmony_ciDESTROY_PREFIX = 'vkDestroy'
134e5c31af7Sopenharmony_ciTYPEENUM = 'VkStructureType'
135e5c31af7Sopenharmony_ci
136e5c31af7Sopenharmony_ciSPECIFICATION_DIR = Path(__file__).parent.parent
137e5c31af7Sopenharmony_ciREVISION_RE = re.compile(r' *[*] Revision (?P<num>[1-9][0-9]*),.*')
138e5c31af7Sopenharmony_ci
139e5c31af7Sopenharmony_ci
140e5c31af7Sopenharmony_cidef get_extension_source(extname):
141e5c31af7Sopenharmony_ci    fn = f'{extname}.adoc'
142e5c31af7Sopenharmony_ci    return str(SPECIFICATION_DIR / 'appendices' / fn)
143e5c31af7Sopenharmony_ci
144e5c31af7Sopenharmony_ci
145e5c31af7Sopenharmony_ciclass EntityDatabase(OrigEntityDatabase):
146e5c31af7Sopenharmony_ci
147e5c31af7Sopenharmony_ci    def __init__(self, args):
148e5c31af7Sopenharmony_ci        """Retain command-line arguments for later use in makeRegistry"""
149e5c31af7Sopenharmony_ci        self.args = args
150e5c31af7Sopenharmony_ci
151e5c31af7Sopenharmony_ci        super().__init__()
152e5c31af7Sopenharmony_ci
153e5c31af7Sopenharmony_ci    # Override base class method to not exclude 'disabled' extensions
154e5c31af7Sopenharmony_ci    def getExclusionSet(self):
155e5c31af7Sopenharmony_ci        """Return a set of "support=" attribute strings that should not be included in the database.
156e5c31af7Sopenharmony_ci
157e5c31af7Sopenharmony_ci        Called only during construction."""
158e5c31af7Sopenharmony_ci
159e5c31af7Sopenharmony_ci        return set(())
160e5c31af7Sopenharmony_ci
161e5c31af7Sopenharmony_ci    def makeRegistry(self):
162e5c31af7Sopenharmony_ci        try:
163e5c31af7Sopenharmony_ci            import lxml.etree as etree
164e5c31af7Sopenharmony_ci            HAS_LXML = True
165e5c31af7Sopenharmony_ci        except ImportError:
166e5c31af7Sopenharmony_ci            HAS_LXML = False
167e5c31af7Sopenharmony_ci        if not HAS_LXML:
168e5c31af7Sopenharmony_ci            return super().makeRegistry()
169e5c31af7Sopenharmony_ci
170e5c31af7Sopenharmony_ci        if len(self.args.files) > 0:
171e5c31af7Sopenharmony_ci            registryFile = self.args.files[0]
172e5c31af7Sopenharmony_ci        else:
173e5c31af7Sopenharmony_ci            registryFile = str(SPECIFICATION_DIR / 'xml/vk.xml')
174e5c31af7Sopenharmony_ci
175e5c31af7Sopenharmony_ci        registry = Registry()
176e5c31af7Sopenharmony_ci        registry.filename = registryFile
177e5c31af7Sopenharmony_ci        registry.loadElementTree(etree.parse(registryFile))
178e5c31af7Sopenharmony_ci        return registry
179e5c31af7Sopenharmony_ci
180e5c31af7Sopenharmony_ci
181e5c31af7Sopenharmony_ciclass Checker(XMLChecker):
182e5c31af7Sopenharmony_ci    def __init__(self, args):
183e5c31af7Sopenharmony_ci        manual_types_to_codes = {
184e5c31af7Sopenharmony_ci            # These are hard-coded "manual" return codes:
185e5c31af7Sopenharmony_ci            # the codes of the value (string, list, or tuple)
186e5c31af7Sopenharmony_ci            # are available for a command if-and-only-if
187e5c31af7Sopenharmony_ci            # the key type is passed as an input.
188e5c31af7Sopenharmony_ci            'VkFormat': 'VK_ERROR_FORMAT_NOT_SUPPORTED'
189e5c31af7Sopenharmony_ci        }
190e5c31af7Sopenharmony_ci        forward_only = {
191e5c31af7Sopenharmony_ci            # Like the above, but these are only valid in the
192e5c31af7Sopenharmony_ci            # "type implies return code" direction
193e5c31af7Sopenharmony_ci        }
194e5c31af7Sopenharmony_ci        reverse_only = {
195e5c31af7Sopenharmony_ci            # like the above, but these are only valid in the
196e5c31af7Sopenharmony_ci            # "return code implies type or its descendant" direction
197e5c31af7Sopenharmony_ci            # "XrDuration": "XR_TIMEOUT_EXPIRED"
198e5c31af7Sopenharmony_ci        }
199e5c31af7Sopenharmony_ci        # Some return codes are related in that only one of a set
200e5c31af7Sopenharmony_ci        # may be returned by a command
201e5c31af7Sopenharmony_ci        # (eg. XR_ERROR_SESSION_RUNNING and XR_ERROR_SESSION_NOT_RUNNING)
202e5c31af7Sopenharmony_ci        self.exclusive_return_code_sets = tuple(
203e5c31af7Sopenharmony_ci            # set(("XR_ERROR_SESSION_NOT_RUNNING", "XR_ERROR_SESSION_RUNNING")),
204e5c31af7Sopenharmony_ci        )
205e5c31af7Sopenharmony_ci
206e5c31af7Sopenharmony_ci        # This is used to report collisions.
207e5c31af7Sopenharmony_ci        conventions = APIConventions()
208e5c31af7Sopenharmony_ci        db = EntityDatabase(args)
209e5c31af7Sopenharmony_ci
210e5c31af7Sopenharmony_ci        self.extension_cmds = get_extension_commands(db.registry)
211e5c31af7Sopenharmony_ci        self.return_codes = get_enum_value_names(db.registry, 'VkResult')
212e5c31af7Sopenharmony_ci        self.structure_types = get_enum_value_names(db.registry, TYPEENUM)
213e5c31af7Sopenharmony_ci
214e5c31af7Sopenharmony_ci        # Dict of entity name to a list of messages to suppress. (Exclude any context data and "Warning:"/"Error:")
215e5c31af7Sopenharmony_ci        # Keys are entity names, values are tuples or lists of message text to suppress.
216e5c31af7Sopenharmony_ci        suppressions = {}
217e5c31af7Sopenharmony_ci
218e5c31af7Sopenharmony_ci        # Structures explicitly allowed to have 'limittype' attributes
219e5c31af7Sopenharmony_ci        self.allowedStructs = set((
220e5c31af7Sopenharmony_ci            'VkFormatProperties',
221e5c31af7Sopenharmony_ci            'VkFormatProperties2',
222e5c31af7Sopenharmony_ci            'VkPhysicalDeviceProperties',
223e5c31af7Sopenharmony_ci            'VkPhysicalDeviceProperties2',
224e5c31af7Sopenharmony_ci            'VkPhysicalDeviceLimits',
225e5c31af7Sopenharmony_ci            'VkQueueFamilyProperties',
226e5c31af7Sopenharmony_ci            'VkQueueFamilyProperties2',
227e5c31af7Sopenharmony_ci            'VkSparseImageFormatProperties',
228e5c31af7Sopenharmony_ci            'VkSparseImageFormatProperties2',
229e5c31af7Sopenharmony_ci        ))
230e5c31af7Sopenharmony_ci
231e5c31af7Sopenharmony_ci        # Substructures of allowed structures. This can be found by looking
232e5c31af7Sopenharmony_ci        # at tags, but there are so few cases that it is hardwired for now.
233e5c31af7Sopenharmony_ci        self.nestedStructs = set((
234e5c31af7Sopenharmony_ci            'VkPhysicalDeviceLimits',
235e5c31af7Sopenharmony_ci            'VkPhysicalDeviceSparseProperties',
236e5c31af7Sopenharmony_ci            'VkPhysicalDeviceProperties',
237e5c31af7Sopenharmony_ci            'VkQueueFamilyProperties',
238e5c31af7Sopenharmony_ci            'VkSparseImageFormatProperties',
239e5c31af7Sopenharmony_ci        ))
240e5c31af7Sopenharmony_ci
241e5c31af7Sopenharmony_ci        # Structures all of whose (non pNext/sType) members are required to
242e5c31af7Sopenharmony_ci        # have 'limittype' attributes, as are their descendants
243e5c31af7Sopenharmony_ci        self.requiredStructs = set((
244e5c31af7Sopenharmony_ci            'VkPhysicalDeviceProperties',
245e5c31af7Sopenharmony_ci            'VkPhysicalDeviceProperties2',
246e5c31af7Sopenharmony_ci            'VkPhysicalDeviceLimits',
247e5c31af7Sopenharmony_ci            'VkSparseImageFormatProperties',
248e5c31af7Sopenharmony_ci            'VkSparseImageFormatProperties2',
249e5c31af7Sopenharmony_ci        ))
250e5c31af7Sopenharmony_ci
251e5c31af7Sopenharmony_ci        # Structures which have already have their limittype attributes validated
252e5c31af7Sopenharmony_ci        self.validatedLimittype = set()
253e5c31af7Sopenharmony_ci
254e5c31af7Sopenharmony_ci        # Initialize superclass
255e5c31af7Sopenharmony_ci        super().__init__(entity_db=db, conventions=conventions,
256e5c31af7Sopenharmony_ci                         manual_types_to_codes=manual_types_to_codes,
257e5c31af7Sopenharmony_ci                         forward_only_types_to_codes=forward_only,
258e5c31af7Sopenharmony_ci                         reverse_only_types_to_codes=reverse_only,
259e5c31af7Sopenharmony_ci                         suppressions=suppressions,
260e5c31af7Sopenharmony_ci                         display_warnings=args.warn)
261e5c31af7Sopenharmony_ci
262e5c31af7Sopenharmony_ci    def check(self):
263e5c31af7Sopenharmony_ci        """Extends base class behavior with additional checks"""
264e5c31af7Sopenharmony_ci
265e5c31af7Sopenharmony_ci        # This test is not run on a per-structure basis, but loops over
266e5c31af7Sopenharmony_ci        # specific structures
267e5c31af7Sopenharmony_ci        self.check_type_required_limittype()
268e5c31af7Sopenharmony_ci
269e5c31af7Sopenharmony_ci        super().check()
270e5c31af7Sopenharmony_ci
271e5c31af7Sopenharmony_ci    def check_command(self, name, info):
272e5c31af7Sopenharmony_ci        """Extends base class behavior with additional checks"""
273e5c31af7Sopenharmony_ci
274e5c31af7Sopenharmony_ci        if name[0:5] == 'vkCmd':
275e5c31af7Sopenharmony_ci            if info.elem.get('tasks') is None:
276e5c31af7Sopenharmony_ci                self.record_error(f'{name} is a vkCmd* command, but is missing a "tasks" attribute')
277e5c31af7Sopenharmony_ci
278e5c31af7Sopenharmony_ci        super().check_command(name, info)
279e5c31af7Sopenharmony_ci
280e5c31af7Sopenharmony_ci    def check_command_return_codes_basic(self, name, info,
281e5c31af7Sopenharmony_ci                                         successcodes, errorcodes):
282e5c31af7Sopenharmony_ci        """Check a command's return codes for consistency.
283e5c31af7Sopenharmony_ci
284e5c31af7Sopenharmony_ci        Called on every command."""
285e5c31af7Sopenharmony_ci        # Check that all extension commands can return the code associated
286e5c31af7Sopenharmony_ci        # with trying to use an extension that was not enabled.
287e5c31af7Sopenharmony_ci        # if name in self.extension_cmds and UNSUPPORTED not in errorcodes:
288e5c31af7Sopenharmony_ci        #     self.record_error('Missing expected return code',
289e5c31af7Sopenharmony_ci        #                       UNSUPPORTED,
290e5c31af7Sopenharmony_ci        #                       'implied due to being an extension command')
291e5c31af7Sopenharmony_ci
292e5c31af7Sopenharmony_ci        codes = successcodes.union(errorcodes)
293e5c31af7Sopenharmony_ci
294e5c31af7Sopenharmony_ci        # Check that all return codes are recognized.
295e5c31af7Sopenharmony_ci        unrecognized = codes - self.return_codes
296e5c31af7Sopenharmony_ci        if unrecognized:
297e5c31af7Sopenharmony_ci            self.record_error('Unrecognized return code(s):',
298e5c31af7Sopenharmony_ci                              unrecognized)
299e5c31af7Sopenharmony_ci
300e5c31af7Sopenharmony_ci        elem = info.elem
301e5c31af7Sopenharmony_ci        params = [(getElemName(elt), elt) for elt in elem.findall('param')]
302e5c31af7Sopenharmony_ci
303e5c31af7Sopenharmony_ci        def is_count_output(name, elt):
304e5c31af7Sopenharmony_ci            # Must end with Count or Size,
305e5c31af7Sopenharmony_ci            # not be const,
306e5c31af7Sopenharmony_ci            # and be a pointer (detected by naming convention)
307e5c31af7Sopenharmony_ci            return (name.endswith('Count') or name.endswith('Size')) \
308e5c31af7Sopenharmony_ci                and (elt.tail is None or 'const' not in elt.tail) \
309e5c31af7Sopenharmony_ci                and (name.startswith('p'))
310e5c31af7Sopenharmony_ci
311e5c31af7Sopenharmony_ci        countParams = [elt
312e5c31af7Sopenharmony_ci                       for name, elt in params
313e5c31af7Sopenharmony_ci                       if is_count_output(name, elt)]
314e5c31af7Sopenharmony_ci        if countParams:
315e5c31af7Sopenharmony_ci            assert(len(countParams) == 1)
316e5c31af7Sopenharmony_ci            if 'VK_INCOMPLETE' not in successcodes:
317e5c31af7Sopenharmony_ci                message = "Apparent enumeration of an array without VK_INCOMPLETE in successcodes for command {}.".format(name)
318e5c31af7Sopenharmony_ci                if name in CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS:
319e5c31af7Sopenharmony_ci                    self.record_warning('(Allowed exception)', message)
320e5c31af7Sopenharmony_ci                else:
321e5c31af7Sopenharmony_ci                    self.record_error(message)
322e5c31af7Sopenharmony_ci
323e5c31af7Sopenharmony_ci        elif 'VK_INCOMPLETE' in successcodes:
324e5c31af7Sopenharmony_ci            message = "VK_INCOMPLETE in successcodes of command {} that is apparently not an array enumeration.".format(name)
325e5c31af7Sopenharmony_ci            if name in CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS:
326e5c31af7Sopenharmony_ci                self.record_warning('(Allowed exception)', message)
327e5c31af7Sopenharmony_ci            else:
328e5c31af7Sopenharmony_ci                self.record_error(message)
329e5c31af7Sopenharmony_ci
330e5c31af7Sopenharmony_ci    def check_param(self, param):
331e5c31af7Sopenharmony_ci        """Check a member of a struct or a param of a function.
332e5c31af7Sopenharmony_ci
333e5c31af7Sopenharmony_ci        Called from check_params."""
334e5c31af7Sopenharmony_ci        super().check_param(param)
335e5c31af7Sopenharmony_ci
336e5c31af7Sopenharmony_ci        if not self.is_api_type(param):
337e5c31af7Sopenharmony_ci            return
338e5c31af7Sopenharmony_ci
339e5c31af7Sopenharmony_ci        param_text = ''.join(param.itertext())
340e5c31af7Sopenharmony_ci        param_name = getElemName(param)
341e5c31af7Sopenharmony_ci
342e5c31af7Sopenharmony_ci        # Make sure the number of leading 'p' matches the pointer count.
343e5c31af7Sopenharmony_ci        pointercount = param.find('type').tail
344e5c31af7Sopenharmony_ci        if pointercount:
345e5c31af7Sopenharmony_ci            pointercount = pointercount.count('*')
346e5c31af7Sopenharmony_ci        if pointercount:
347e5c31af7Sopenharmony_ci            prefix = 'p' * pointercount
348e5c31af7Sopenharmony_ci            if not param_name.startswith(prefix):
349e5c31af7Sopenharmony_ci                param_type = param.find('type').text
350e5c31af7Sopenharmony_ci                message = "Apparently incorrect pointer-related name prefix for {} - expected it to start with '{}'".format(
351e5c31af7Sopenharmony_ci                    param_text, prefix)
352e5c31af7Sopenharmony_ci                if (self.entity, param_type, param_name) in CHECK_PARAM_POINTER_NAME_EXCEPTIONS:
353e5c31af7Sopenharmony_ci                    self.record_warning('(Allowed exception)', message, elem=param)
354e5c31af7Sopenharmony_ci                else:
355e5c31af7Sopenharmony_ci                    self.record_error(message, elem=param)
356e5c31af7Sopenharmony_ci
357e5c31af7Sopenharmony_ci        # Make sure no members have optional="false" attributes
358e5c31af7Sopenharmony_ci        optional = param.get('optional')
359e5c31af7Sopenharmony_ci        if optional == 'false':
360e5c31af7Sopenharmony_ci            message = f'{self.entity}.{param_name} member has disallowed \'optional="false"\' attribute (remove this attribute)'
361e5c31af7Sopenharmony_ci            self.record_error(message, elem=param)
362e5c31af7Sopenharmony_ci
363e5c31af7Sopenharmony_ci        # Make sure pNext members have optional="true" attributes
364e5c31af7Sopenharmony_ci        if param_name == self.conventions.nextpointer_member_name:
365e5c31af7Sopenharmony_ci            optional = param.get('optional')
366e5c31af7Sopenharmony_ci            if optional is None or optional != 'true':
367e5c31af7Sopenharmony_ci                message = f'{self.entity}.pNext member is missing \'optional="true"\' attribute'
368e5c31af7Sopenharmony_ci                if self.entity in CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS:
369e5c31af7Sopenharmony_ci                    self.record_warning('(Allowed exception)', message, elem=param)
370e5c31af7Sopenharmony_ci                else:
371e5c31af7Sopenharmony_ci                    self.record_error(message, elem=param)
372e5c31af7Sopenharmony_ci
373e5c31af7Sopenharmony_ci    def check_type_stype(self, name, info, type_elts):
374e5c31af7Sopenharmony_ci        """Check a struct type's sType member"""
375e5c31af7Sopenharmony_ci        if len(type_elts) > 1:
376e5c31af7Sopenharmony_ci            self.record_error(
377e5c31af7Sopenharmony_ci                'Have more than one member of type', TYPEENUM)
378e5c31af7Sopenharmony_ci        else:
379e5c31af7Sopenharmony_ci            type_elt = type_elts[0]
380e5c31af7Sopenharmony_ci            val = type_elt.get('values')
381e5c31af7Sopenharmony_ci            if val and val not in self.structure_types:
382e5c31af7Sopenharmony_ci                message = f'{self.entity} has unknown structure type constant {val}'
383e5c31af7Sopenharmony_ci                if val in CHECK_TYPE_STYPE_EXCEPTIONS:
384e5c31af7Sopenharmony_ci                    self.record_warning('(Allowed exception)', message)
385e5c31af7Sopenharmony_ci                else:
386e5c31af7Sopenharmony_ci                    self.record_error(message)
387e5c31af7Sopenharmony_ci
388e5c31af7Sopenharmony_ci    def check_type_pnext(self, name, info):
389e5c31af7Sopenharmony_ci        """Check a struct type's pNext member, if present"""
390e5c31af7Sopenharmony_ci
391e5c31af7Sopenharmony_ci        next_name = self.conventions.nextpointer_member_name
392e5c31af7Sopenharmony_ci        next_member = findNamedElem(info.elem.findall('member'), next_name)
393e5c31af7Sopenharmony_ci        if next_member is not None:
394e5c31af7Sopenharmony_ci            # Ensure that the 'optional' attribute is set to 'true'
395e5c31af7Sopenharmony_ci            optional = next_member.get('optional')
396e5c31af7Sopenharmony_ci            if optional is None or optional != 'true':
397e5c31af7Sopenharmony_ci                message = f'{name}.{next_name} member is missing \'optional="true"\' attribute'
398e5c31af7Sopenharmony_ci                if name in CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS:
399e5c31af7Sopenharmony_ci                    self.record_warning('(Allowed exception)', message)
400e5c31af7Sopenharmony_ci                else:
401e5c31af7Sopenharmony_ci                    self.record_error(message)
402e5c31af7Sopenharmony_ci
403e5c31af7Sopenharmony_ci    def __isLimittypeStruct(self, name, info, allowedStructs):
404e5c31af7Sopenharmony_ci        """Check if a type element is a structure allowed to have 'limittype' attributes
405e5c31af7Sopenharmony_ci           name - name of a structure
406e5c31af7Sopenharmony_ci           info - corresponding TypeInfo object
407e5c31af7Sopenharmony_ci           allowedStructs - set of struct names explicitly allowed"""
408e5c31af7Sopenharmony_ci
409e5c31af7Sopenharmony_ci        # Is this an explicitly allowed struct?
410e5c31af7Sopenharmony_ci        if name in allowedStructs:
411e5c31af7Sopenharmony_ci            return True
412e5c31af7Sopenharmony_ci
413e5c31af7Sopenharmony_ci        # Is this a struct extending an explicitly allowed struct?
414e5c31af7Sopenharmony_ci        extends = info.elem.get('structextends')
415e5c31af7Sopenharmony_ci        if extends is not None:
416e5c31af7Sopenharmony_ci            # See if any name in the structextends attribute is an allowed
417e5c31af7Sopenharmony_ci            # struct
418e5c31af7Sopenharmony_ci            if len(set(extends.split(',')) & allowedStructs) > 0:
419e5c31af7Sopenharmony_ci                return True
420e5c31af7Sopenharmony_ci
421e5c31af7Sopenharmony_ci        return False
422e5c31af7Sopenharmony_ci
423e5c31af7Sopenharmony_ci    def __validateStructLimittypes(self, name, info, requiredLimittype):
424e5c31af7Sopenharmony_ci        """Validate 'limittype' attributes for a single struct.
425e5c31af7Sopenharmony_ci           info - TypeInfo for a struct <type>
426e5c31af7Sopenharmony_ci           requiredLimittype - True if members *must* have a limittype"""
427e5c31af7Sopenharmony_ci
428e5c31af7Sopenharmony_ci        # Do not re-check structures
429e5c31af7Sopenharmony_ci        if name in self.validatedLimittype:
430e5c31af7Sopenharmony_ci            return {}
431e5c31af7Sopenharmony_ci        self.validatedLimittype.add(name)
432e5c31af7Sopenharmony_ci
433e5c31af7Sopenharmony_ci        limittypeDiags = namedtuple('limittypeDiags', ['missing', 'invalid'])
434e5c31af7Sopenharmony_ci        badFields = defaultdict(lambda : limittypeDiags(missing=[], invalid=[]))
435e5c31af7Sopenharmony_ci        validLimittypes = { 'min', 'max', 'not', 'pot', 'mul', 'bits', 'bitmask', 'range', 'struct', 'exact', 'noauto' }
436e5c31af7Sopenharmony_ci        for member in info.getMembers():
437e5c31af7Sopenharmony_ci            memberName = member.findtext('name')
438e5c31af7Sopenharmony_ci            if memberName in ['sType', 'pNext']:
439e5c31af7Sopenharmony_ci                continue
440e5c31af7Sopenharmony_ci            limittype = member.get('limittype')
441e5c31af7Sopenharmony_ci            if limittype is None:
442e5c31af7Sopenharmony_ci                # Do not tag this as missing if it is not required
443e5c31af7Sopenharmony_ci                if requiredLimittype:
444e5c31af7Sopenharmony_ci                    badFields[info.elem.get('name')].missing.append(memberName)
445e5c31af7Sopenharmony_ci            elif limittype == 'struct':
446e5c31af7Sopenharmony_ci                typeName = member.findtext('type')
447e5c31af7Sopenharmony_ci                memberType = self.reg.typedict[typeName]
448e5c31af7Sopenharmony_ci                badFields.update(self.__validateStructLimittypes(typeName, memberType, requiredLimittype))
449e5c31af7Sopenharmony_ci            else:
450e5c31af7Sopenharmony_ci                for value in limittype.split(','):
451e5c31af7Sopenharmony_ci                    if value not in validLimittypes:
452e5c31af7Sopenharmony_ci                        badFields[info.elem.get('name')].invalid.append(memberName)
453e5c31af7Sopenharmony_ci
454e5c31af7Sopenharmony_ci        return badFields
455e5c31af7Sopenharmony_ci
456e5c31af7Sopenharmony_ci    def check_type_disallowed_limittype(self, name, info):
457e5c31af7Sopenharmony_ci        """Check if a struct type's members cannot have the 'limittype' attribute"""
458e5c31af7Sopenharmony_ci
459e5c31af7Sopenharmony_ci        # If not allowed to have limittypes, verify this for each member
460e5c31af7Sopenharmony_ci        if not self.__isLimittypeStruct(name, info, self.allowedStructs.union(self.nestedStructs)):
461e5c31af7Sopenharmony_ci            for member in info.getMembers():
462e5c31af7Sopenharmony_ci                if member.get('limittype') is not None:
463e5c31af7Sopenharmony_ci                    memname = member.findtext('name')
464e5c31af7Sopenharmony_ci                    self.record_error(f'{name} member {memname} has disallowed limittype attribute')
465e5c31af7Sopenharmony_ci
466e5c31af7Sopenharmony_ci    def check_type_optional_value(self, name, info):
467e5c31af7Sopenharmony_ci        """Check if a struct type's members have disallowed 'optional' attribute values"""
468e5c31af7Sopenharmony_ci
469e5c31af7Sopenharmony_ci        for member in info.getMembers():
470e5c31af7Sopenharmony_ci            # Make sure no members have optional="false" attributes
471e5c31af7Sopenharmony_ci            optional = member.get('optional')
472e5c31af7Sopenharmony_ci            if optional == 'false':
473e5c31af7Sopenharmony_ci                memname = member.findtext('name')
474e5c31af7Sopenharmony_ci                message = f'{name} member {memname} has disallowed \'optional="false"\' attribute (remove this attribute)'
475e5c31af7Sopenharmony_ci                self.record_error(message, elem=member)
476e5c31af7Sopenharmony_ci
477e5c31af7Sopenharmony_ci    def check_type_required_limittype(self):
478e5c31af7Sopenharmony_ci        """Check struct type members which must have the 'limittype' attribute
479e5c31af7Sopenharmony_ci
480e5c31af7Sopenharmony_ci        Called from check."""
481e5c31af7Sopenharmony_ci
482e5c31af7Sopenharmony_ci        for name in self.allowedStructs:
483e5c31af7Sopenharmony_ci            # Assume that only extending structs of structs explicitly
484e5c31af7Sopenharmony_ci            # requiring limittypes also require them
485e5c31af7Sopenharmony_ci            requiredLimittype = (name in self.requiredStructs)
486e5c31af7Sopenharmony_ci
487e5c31af7Sopenharmony_ci            info = self.reg.typedict[name]
488e5c31af7Sopenharmony_ci
489e5c31af7Sopenharmony_ci            self.set_error_context(entity=name, elem=info.elem)
490e5c31af7Sopenharmony_ci
491e5c31af7Sopenharmony_ci            badFields = self.__validateStructLimittypes(name, info, requiredLimittype)
492e5c31af7Sopenharmony_ci            for extendingStructName in self.reg.validextensionstructs[name]:
493e5c31af7Sopenharmony_ci                extendingStruct = self.reg.typedict[extendingStructName]
494e5c31af7Sopenharmony_ci                badFields.update(self.__validateStructLimittypes(extendingStructName, extendingStruct, requiredLimittype))
495e5c31af7Sopenharmony_ci
496e5c31af7Sopenharmony_ci            if badFields:
497e5c31af7Sopenharmony_ci                for key in sorted(badFields.keys()):
498e5c31af7Sopenharmony_ci                    diags = badFields[key]
499e5c31af7Sopenharmony_ci                    if diags.missing:
500e5c31af7Sopenharmony_ci                        self.record_error(f'{name} missing limittype for members {", ".join(badFields[key].missing)}')
501e5c31af7Sopenharmony_ci                    if diags.invalid:
502e5c31af7Sopenharmony_ci                        self.record_error(f'{name} has invalid limittype for members {", ".join(badFields[key].invalid)}')
503e5c31af7Sopenharmony_ci
504e5c31af7Sopenharmony_ci    def check_type(self, name, info, category):
505e5c31af7Sopenharmony_ci        """Check a type's XML data for consistency.
506e5c31af7Sopenharmony_ci
507e5c31af7Sopenharmony_ci        Called from check."""
508e5c31af7Sopenharmony_ci
509e5c31af7Sopenharmony_ci        if category == 'struct':
510e5c31af7Sopenharmony_ci            type_elts = [elt
511e5c31af7Sopenharmony_ci                         for elt in info.elem.findall('member')
512e5c31af7Sopenharmony_ci                         if getElemType(elt) == TYPEENUM]
513e5c31af7Sopenharmony_ci
514e5c31af7Sopenharmony_ci            if type_elts:
515e5c31af7Sopenharmony_ci                self.check_type_stype(name, info, type_elts)
516e5c31af7Sopenharmony_ci                self.check_type_pnext(name, info)
517e5c31af7Sopenharmony_ci
518e5c31af7Sopenharmony_ci            # Check for disallowed limittypes on all structures
519e5c31af7Sopenharmony_ci            self.check_type_disallowed_limittype(name, info)
520e5c31af7Sopenharmony_ci
521e5c31af7Sopenharmony_ci            # Check for disallowed 'optional' values
522e5c31af7Sopenharmony_ci            self.check_type_optional_value(name, info)
523e5c31af7Sopenharmony_ci        elif category == 'bitmask':
524e5c31af7Sopenharmony_ci            if 'Flags' in name:
525e5c31af7Sopenharmony_ci                expected_require = name.replace('Flags', 'FlagBits')
526e5c31af7Sopenharmony_ci                require = info.elem.get('require')
527e5c31af7Sopenharmony_ci                if require is not None and expected_require != require:
528e5c31af7Sopenharmony_ci                    self.record_error('Unexpected require attribute value:',
529e5c31af7Sopenharmony_ci                                      'got', require,
530e5c31af7Sopenharmony_ci                                      'but expected', expected_require)
531e5c31af7Sopenharmony_ci        super().check_type(name, info, category)
532e5c31af7Sopenharmony_ci
533e5c31af7Sopenharmony_ci    def check_suffixes(self, name, info, supported, name_exceptions):
534e5c31af7Sopenharmony_ci        """Check suffixes of new APIs required by an extension, which should
535e5c31af7Sopenharmony_ci           match the author ID of the extension.
536e5c31af7Sopenharmony_ci
537e5c31af7Sopenharmony_ci           Called from check_extension.
538e5c31af7Sopenharmony_ci
539e5c31af7Sopenharmony_ci           name - extension name
540e5c31af7Sopenharmony_ci           info - extdict entry for name
541e5c31af7Sopenharmony_ci           supported - True if extension supported by API being checked
542e5c31af7Sopenharmony_ci           name_exceptions - set of API names to not check, in addition to
543e5c31af7Sopenharmony_ci                             the global exception list."""
544e5c31af7Sopenharmony_ci
545e5c31af7Sopenharmony_ci        def has_suffix(apiname, author):
546e5c31af7Sopenharmony_ci            return apiname[-len(author):] == author
547e5c31af7Sopenharmony_ci
548e5c31af7Sopenharmony_ci        def has_any_suffixes(apiname, authors):
549e5c31af7Sopenharmony_ci            for author in authors:
550e5c31af7Sopenharmony_ci                if has_suffix(apiname, author):
551e5c31af7Sopenharmony_ci                    return True
552e5c31af7Sopenharmony_ci            return False
553e5c31af7Sopenharmony_ci
554e5c31af7Sopenharmony_ci        def check_names(elems, author, alt_authors, name_exceptions):
555e5c31af7Sopenharmony_ci            """Check names in a list of elements for consistency
556e5c31af7Sopenharmony_ci
557e5c31af7Sopenharmony_ci               elems - list of elements to check
558e5c31af7Sopenharmony_ci               author - author ID of the <extension> tag
559e5c31af7Sopenharmony_ci               alt_authors - set of other allowed author IDs
560e5c31af7Sopenharmony_ci               name_exceptions - additional set of allowed exceptions"""
561e5c31af7Sopenharmony_ci
562e5c31af7Sopenharmony_ci            for elem in elems:
563e5c31af7Sopenharmony_ci                apiname = elem.get('name', 'NO NAME ATTRIBUTE')
564e5c31af7Sopenharmony_ci                suffix = apiname[-len(author):]
565e5c31af7Sopenharmony_ci
566e5c31af7Sopenharmony_ci                if (not has_suffix(apiname, author) and
567e5c31af7Sopenharmony_ci                    apiname not in EXTENSION_API_NAME_EXCEPTIONS and
568e5c31af7Sopenharmony_ci                    apiname not in name_exceptions):
569e5c31af7Sopenharmony_ci
570e5c31af7Sopenharmony_ci                    msg = f'Extension {name} <{elem.tag}> {apiname} does not have expected suffix {author}'
571e5c31af7Sopenharmony_ci
572e5c31af7Sopenharmony_ci                    # Explicit 'aliased' deprecations not matching the
573e5c31af7Sopenharmony_ci                    # naming rules are allowed, but warned.
574e5c31af7Sopenharmony_ci                    if has_any_suffixes(apiname, alt_authors):
575e5c31af7Sopenharmony_ci                        self.record_warning('Allowed alternate author ID:', msg)
576e5c31af7Sopenharmony_ci                    elif not supported:
577e5c31af7Sopenharmony_ci                        self.record_warning('Allowed inconsistency for disabled extension:', msg)
578e5c31af7Sopenharmony_ci                    elif elem.get('deprecated') == 'aliased':
579e5c31af7Sopenharmony_ci                        self.record_warning('Allowed aliasing deprecation:', msg)
580e5c31af7Sopenharmony_ci                    else:
581e5c31af7Sopenharmony_ci                        msg += '\n\
582e5c31af7Sopenharmony_ciThis may be due to an extension interaction not having the correct <require depends="">\n\
583e5c31af7Sopenharmony_ciOther exceptions can be added to xml_consistency.py:EXTENSION_API_NAME_EXCEPTIONS'
584e5c31af7Sopenharmony_ci                        self.record_error(msg)
585e5c31af7Sopenharmony_ci
586e5c31af7Sopenharmony_ci        elem = info.elem
587e5c31af7Sopenharmony_ci
588e5c31af7Sopenharmony_ci        self.set_error_context(entity=name, elem=elem)
589e5c31af7Sopenharmony_ci
590e5c31af7Sopenharmony_ci        # Extract author ID from the extension name.
591e5c31af7Sopenharmony_ci        author = name.split('_')[1]
592e5c31af7Sopenharmony_ci
593e5c31af7Sopenharmony_ci        # Loop over each <require> tag checking the API name suffixes in
594e5c31af7Sopenharmony_ci        # that tag for consistency.
595e5c31af7Sopenharmony_ci        # Names in tags whose 'depends' attribute includes extensions with
596e5c31af7Sopenharmony_ci        # different author IDs may be suffixed with those IDs.
597e5c31af7Sopenharmony_ci        for req_elem in elem.findall('./require'):
598e5c31af7Sopenharmony_ci            depends = req_elem.get('depends', '')
599e5c31af7Sopenharmony_ci            alt_authors = set()
600e5c31af7Sopenharmony_ci            if len(depends) > 0:
601e5c31af7Sopenharmony_ci                for name in dependencyNames(depends):
602e5c31af7Sopenharmony_ci                    # Skip core versions
603e5c31af7Sopenharmony_ci                    if name[0:11] != 'VK_VERSION_':
604e5c31af7Sopenharmony_ci                        # Extract author ID from extension name
605e5c31af7Sopenharmony_ci                        id = name.split('_')[1]
606e5c31af7Sopenharmony_ci                        alt_authors.add(id)
607e5c31af7Sopenharmony_ci
608e5c31af7Sopenharmony_ci            check_names(req_elem.findall('./command'), author, alt_authors, name_exceptions)
609e5c31af7Sopenharmony_ci            check_names(req_elem.findall('./type'), author, alt_authors, name_exceptions)
610e5c31af7Sopenharmony_ci            check_names(req_elem.findall('./enum'), author, alt_authors, name_exceptions)
611e5c31af7Sopenharmony_ci
612e5c31af7Sopenharmony_ci    def check_extension(self, name, info, supported):
613e5c31af7Sopenharmony_ci        """Check an extension's XML data for consistency.
614e5c31af7Sopenharmony_ci
615e5c31af7Sopenharmony_ci        Called from check."""
616e5c31af7Sopenharmony_ci
617e5c31af7Sopenharmony_ci        elem = info.elem
618e5c31af7Sopenharmony_ci        enums = elem.findall('./require/enum[@name]')
619e5c31af7Sopenharmony_ci
620e5c31af7Sopenharmony_ci        # If extension name is not on the exception list and matches the
621e5c31af7Sopenharmony_ci        # versioned-extension pattern, map the extension name to the version
622e5c31af7Sopenharmony_ci        # name with the version as a separate word. Otherwise just map it to
623e5c31af7Sopenharmony_ci        # the upper-case version of the extension name.
624e5c31af7Sopenharmony_ci
625e5c31af7Sopenharmony_ci        matches = EXTNAME_RE.fullmatch(name)
626e5c31af7Sopenharmony_ci        ext_versioned_name = False
627e5c31af7Sopenharmony_ci        if name in EXTENSION_ENUM_NAME_SPELLING_CHANGE:
628e5c31af7Sopenharmony_ci            ext_enum_name = EXTENSION_ENUM_NAME_SPELLING_CHANGE.get(name)
629e5c31af7Sopenharmony_ci        elif matches is None or name in EXTENSION_NAME_VERSION_EXCEPTIONS:
630e5c31af7Sopenharmony_ci            # This is the usual case, either a name that does not look
631e5c31af7Sopenharmony_ci            # versioned, or one that does but is on the exception list.
632e5c31af7Sopenharmony_ci            ext_enum_name = name.upper()
633e5c31af7Sopenharmony_ci        else:
634e5c31af7Sopenharmony_ci            # This is a versioned extension name.
635e5c31af7Sopenharmony_ci            # Treat the version number as a separate word.
636e5c31af7Sopenharmony_ci            base = matches.group('base')
637e5c31af7Sopenharmony_ci            version = matches.group('version')
638e5c31af7Sopenharmony_ci            ext_enum_name = base.upper() + '_' + version
639e5c31af7Sopenharmony_ci            # Keep track of this case
640e5c31af7Sopenharmony_ci            ext_versioned_name = True
641e5c31af7Sopenharmony_ci
642e5c31af7Sopenharmony_ci        # Look for the expected SPEC_VERSION token name
643e5c31af7Sopenharmony_ci        version_name = f'{ext_enum_name}_SPEC_VERSION'
644e5c31af7Sopenharmony_ci        version_elem = findNamedElem(enums, version_name)
645e5c31af7Sopenharmony_ci
646e5c31af7Sopenharmony_ci        if version_elem is None:
647e5c31af7Sopenharmony_ci            # Did not find a SPEC_VERSION enum matching the extension name
648e5c31af7Sopenharmony_ci            if ext_versioned_name:
649e5c31af7Sopenharmony_ci                suffix = '\n\
650e5c31af7Sopenharmony_ci    Make sure that trailing version numbers in extension names are treated\n\
651e5c31af7Sopenharmony_ci    as separate words in extension enumerant names. If this is an extension\n\
652e5c31af7Sopenharmony_ci    whose name ends in a number which is not a version, such as "...h264"\n\
653e5c31af7Sopenharmony_ci    or "...int16", add it to EXTENSION_NAME_VERSION_EXCEPTIONS in\n\
654e5c31af7Sopenharmony_ci    scripts/xml_consistency.py.'
655e5c31af7Sopenharmony_ci            else:
656e5c31af7Sopenharmony_ci                suffix = ''
657e5c31af7Sopenharmony_ci            self.record_error(f'Missing version enum {version_name}{suffix}')
658e5c31af7Sopenharmony_ci        elif supported:
659e5c31af7Sopenharmony_ci            # Skip unsupported / disabled extensions for these checks
660e5c31af7Sopenharmony_ci
661e5c31af7Sopenharmony_ci            fn = get_extension_source(name)
662e5c31af7Sopenharmony_ci            revisions = []
663e5c31af7Sopenharmony_ci            with open(fn, 'r', encoding='utf-8') as fp:
664e5c31af7Sopenharmony_ci                for line in fp:
665e5c31af7Sopenharmony_ci                    line = line.rstrip()
666e5c31af7Sopenharmony_ci                    match = REVISION_RE.match(line)
667e5c31af7Sopenharmony_ci                    if match:
668e5c31af7Sopenharmony_ci                        revisions.append(int(match.group('num')))
669e5c31af7Sopenharmony_ci            ver_from_xml = version_elem.get('value')
670e5c31af7Sopenharmony_ci            if revisions:
671e5c31af7Sopenharmony_ci                ver_from_text = str(max(revisions))
672e5c31af7Sopenharmony_ci                if ver_from_xml != ver_from_text:
673e5c31af7Sopenharmony_ci                    self.record_error('Version enum mismatch: spec text indicates', ver_from_text,
674e5c31af7Sopenharmony_ci                                      'but XML says', ver_from_xml)
675e5c31af7Sopenharmony_ci            else:
676e5c31af7Sopenharmony_ci                if ver_from_xml == '1':
677e5c31af7Sopenharmony_ci                    self.record_warning(
678e5c31af7Sopenharmony_ci                        "Cannot find version history in spec text - make sure it has lines starting exactly like '  * Revision 1, ....'",
679e5c31af7Sopenharmony_ci                        filename=fn)
680e5c31af7Sopenharmony_ci                else:
681e5c31af7Sopenharmony_ci                    self.record_warning("Cannot find version history in spec text, but XML reports a non-1 version number", ver_from_xml,
682e5c31af7Sopenharmony_ci                                        " - make sure the spec text has lines starting exactly like '  * Revision 1, ....'",
683e5c31af7Sopenharmony_ci                                        filename=fn)
684e5c31af7Sopenharmony_ci
685e5c31af7Sopenharmony_ci            for enum in enums:
686e5c31af7Sopenharmony_ci                enum_name = enum.get('name')
687e5c31af7Sopenharmony_ci                if '_RESERVED_' in enum_name and enum_name not in EXTENSION_NAME_RESERVED_EXCEPTIONS:
688e5c31af7Sopenharmony_ci                    self.record_error(enum_name, 'should not contain _RESERVED_ for a supported extension.\n\
689e5c31af7Sopenharmony_ciIf this is intentional, add it to EXTENSION_NAME_RESERVED_EXCEPTIONS in scripts/xml_consistency.py.')
690e5c31af7Sopenharmony_ci
691e5c31af7Sopenharmony_ci        name_define = f'{ext_enum_name}_EXTENSION_NAME'
692e5c31af7Sopenharmony_ci        name_elem = findNamedElem(enums, name_define)
693e5c31af7Sopenharmony_ci        if name_elem is None:
694e5c31af7Sopenharmony_ci            self.record_error('Missing name enum', name_define)
695e5c31af7Sopenharmony_ci        else:
696e5c31af7Sopenharmony_ci            # Note: etree handles the XML entities here and turns &quot; back into "
697e5c31af7Sopenharmony_ci            expected_name = f'"{name}"'
698e5c31af7Sopenharmony_ci            name_val = name_elem.get('value')
699e5c31af7Sopenharmony_ci            if name_val != expected_name:
700e5c31af7Sopenharmony_ci                self.record_error('Incorrect name enum: expected', expected_name,
701e5c31af7Sopenharmony_ci                                  'got', name_val)
702e5c31af7Sopenharmony_ci
703e5c31af7Sopenharmony_ci        self.check_suffixes(name, info, supported, { version_name, name_define })
704e5c31af7Sopenharmony_ci
705e5c31af7Sopenharmony_ci        # More general checks
706e5c31af7Sopenharmony_ci        super().check_extension(name, info, supported)
707e5c31af7Sopenharmony_ci
708e5c31af7Sopenharmony_ci    def check_format(self):
709e5c31af7Sopenharmony_ci        """Check an extension's XML data for consistency.
710e5c31af7Sopenharmony_ci
711e5c31af7Sopenharmony_ci        Called from check."""
712e5c31af7Sopenharmony_ci
713e5c31af7Sopenharmony_ci        astc3d_formats = [
714e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT',
715e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT',
716e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT',
717e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT',
718e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT',
719e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT',
720e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT',
721e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT',
722e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT',
723e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT',
724e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT',
725e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT',
726e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT',
727e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT',
728e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT',
729e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT',
730e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT',
731e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT',
732e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT',
733e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT',
734e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT',
735e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT',
736e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT',
737e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT',
738e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT',
739e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT',
740e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT',
741e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT',
742e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT',
743e5c31af7Sopenharmony_ci                'VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT'
744e5c31af7Sopenharmony_ci        ]
745e5c31af7Sopenharmony_ci
746e5c31af7Sopenharmony_ci        # Need to build list of formats from rest of <enums>
747e5c31af7Sopenharmony_ci        enum_formats = []
748e5c31af7Sopenharmony_ci        for enum in self.reg.groupdict['VkFormat'].elem:
749e5c31af7Sopenharmony_ci            if enum.get('alias') is None and enum.get('name') != 'VK_FORMAT_UNDEFINED':
750e5c31af7Sopenharmony_ci                enum_formats.append(enum.get('name'))
751e5c31af7Sopenharmony_ci
752e5c31af7Sopenharmony_ci        found_formats = []
753e5c31af7Sopenharmony_ci        for name, info in self.reg.formatsdict.items():
754e5c31af7Sopenharmony_ci            found_formats.append(name)
755e5c31af7Sopenharmony_ci            self.set_error_context(entity=name, elem=info.elem)
756e5c31af7Sopenharmony_ci
757e5c31af7Sopenharmony_ci            if name not in enum_formats:
758e5c31af7Sopenharmony_ci                self.record_error('The <format> has no matching <enum> for', name)
759e5c31af7Sopenharmony_ci
760e5c31af7Sopenharmony_ci            # Check never just 1 plane
761e5c31af7Sopenharmony_ci            plane_elems = info.elem.findall('plane')
762e5c31af7Sopenharmony_ci            if len(plane_elems) == 1:
763e5c31af7Sopenharmony_ci                self.record_error('The <format> has only 1 <plane> for', name)
764e5c31af7Sopenharmony_ci
765e5c31af7Sopenharmony_ci            valid_chroma = ['420', '422', '444']
766e5c31af7Sopenharmony_ci            if info.elem.get('chroma') and info.elem.get('chroma') not in valid_chroma:
767e5c31af7Sopenharmony_ci                self.record_error('The <format> has chroma is not a valid value for', name)
768e5c31af7Sopenharmony_ci
769e5c31af7Sopenharmony_ci            # The formatsgenerator.py assumes only 1 <spirvimageformat> tag.
770e5c31af7Sopenharmony_ci            # If this changes in the future, remove this warning and update generator script
771e5c31af7Sopenharmony_ci            spirv_image_format = info.elem.findall('spirvimageformat')
772e5c31af7Sopenharmony_ci            if len(spirv_image_format) > 1:
773e5c31af7Sopenharmony_ci                self.record_error('More than 1 <spirvimageformat> but formatsgenerator.py is not updated, for format', name)
774e5c31af7Sopenharmony_ci
775e5c31af7Sopenharmony_ci        # Re-loop to check the other way if the <format> is missing
776e5c31af7Sopenharmony_ci        for enum in self.reg.groupdict['VkFormat'].elem:
777e5c31af7Sopenharmony_ci            name = enum.get('name')
778e5c31af7Sopenharmony_ci            if enum.get('alias') is None and name != 'VK_FORMAT_UNDEFINED':
779e5c31af7Sopenharmony_ci                if name not in found_formats and name not in astc3d_formats:
780e5c31af7Sopenharmony_ci                    self.set_error_context(entity=name, elem=enum)
781e5c31af7Sopenharmony_ci                    self.record_error('The <enum> has no matching <format> for ', name)
782e5c31af7Sopenharmony_ci
783e5c31af7Sopenharmony_ci        super().check_format()
784e5c31af7Sopenharmony_ci
785e5c31af7Sopenharmony_ci        # This should be called from check() but as a first pass, do it here
786e5c31af7Sopenharmony_ci        # Check for invalid version names in e.g.
787e5c31af7Sopenharmony_ci        #    <enable version="VK_VERSION_1_2"/>
788e5c31af7Sopenharmony_ci        # Could also consistency check struct / extension tags here
789e5c31af7Sopenharmony_ci        for capname in self.reg.spirvcapdict:
790e5c31af7Sopenharmony_ci            for elem in self.reg.spirvcapdict[capname].elem.findall('enable'):
791e5c31af7Sopenharmony_ci                version = elem.get('version')
792e5c31af7Sopenharmony_ci                if version is not None and version not in self.reg.apidict:
793e5c31af7Sopenharmony_ci                    self.set_error_context(entity=capname, elem=elem)
794e5c31af7Sopenharmony_ci                    self.record_error(f'<spirvcapability> {capname} enabled by a nonexistent version {version}')
795e5c31af7Sopenharmony_ci
796e5c31af7Sopenharmony_ciif __name__ == '__main__':
797e5c31af7Sopenharmony_ci
798e5c31af7Sopenharmony_ci    parser = argparse.ArgumentParser()
799e5c31af7Sopenharmony_ci    parser.add_argument('-warn', action='store_true',
800e5c31af7Sopenharmony_ci                        help='Enable display of warning messages')
801e5c31af7Sopenharmony_ci    parser.add_argument('files', metavar='filename', nargs='*',
802e5c31af7Sopenharmony_ci                        help='XML filename to check')
803e5c31af7Sopenharmony_ci
804e5c31af7Sopenharmony_ci    args = parser.parse_args()
805e5c31af7Sopenharmony_ci
806e5c31af7Sopenharmony_ci    ckr = Checker(args)
807e5c31af7Sopenharmony_ci    ckr.check()
808e5c31af7Sopenharmony_ci
809e5c31af7Sopenharmony_ci    if ckr.fail:
810e5c31af7Sopenharmony_ci        sys.exit(1)
811