diff options
Diffstat (limited to 'codegen/vulkan/scripts/validitygenerator.py')
-rwxr-xr-x | codegen/vulkan/scripts/validitygenerator.py | 1497 |
1 files changed, 0 insertions, 1497 deletions
diff --git a/codegen/vulkan/scripts/validitygenerator.py b/codegen/vulkan/scripts/validitygenerator.py deleted file mode 100755 index a3a84561..00000000 --- a/codegen/vulkan/scripts/validitygenerator.py +++ /dev/null @@ -1,1497 +0,0 @@ -#!/usr/bin/python3 -i -# -# Copyright 2013-2021 The Khronos Group Inc. -# -# SPDX-License-Identifier: Apache-2.0 - -import re -from collections import OrderedDict, namedtuple -from functools import reduce -from pathlib import Path - -from conventions import ProseListFormats as plf -from generator import OutputGenerator, write -from spec_tools.attributes import ExternSyncEntry, LengthEntry -from spec_tools.util import (findNamedElem, findNamedObject, findTypedElem, - getElemName, getElemType) -from spec_tools.validity import ValidityCollection, ValidityEntry - - -# For parsing/splitting queue bit names - Vulkan only -QUEUE_BITS_RE = re.compile(r'([^,]+)') - - -class UnhandledCaseError(RuntimeError): - def __init__(self, msg=None): - if msg: - super().__init__('Got a case in the validity generator that we have not explicitly handled: ' + msg) - else: - super().__init__('Got a case in the validity generator that we have not explicitly handled.') - - -def _genericIterateIntersection(a, b): - """Iterate through all elements in a that are also in b. - - Somewhat like a set's intersection(), - but not type-specific so it can work with OrderedDicts, etc. - It also returns a generator instead of a set, - so you can pick what container type you'd like, - if any. - """ - return (x for x in a if x in b) - - -def _make_ordered_dict(gen): - """Make an ordered dict (with None as the values) from a generator.""" - return OrderedDict(((x, None) for x in gen)) - - -def _orderedDictIntersection(a, b): - return _make_ordered_dict(_genericIterateIntersection(a, b)) - - -def _genericIsDisjoint(a, b): - """Return true if nothing in a is also in b. - - Like a set's is_disjoint(), - but not type-specific so it can work with OrderedDicts, etc. - """ - for _ in _genericIterateIntersection(a, b): - return False - # if we never enter the loop... - return True - - -def _parse_queue_bits(cmd): - """Return a generator of queue bits, with underscores turned to spaces. - - Vulkan-only. - - Return None if the queues attribute is not specified.""" - queuetypes = cmd.get('queues') - if not queuetypes: - return None - return (qt.replace('_', ' ') - for qt in QUEUE_BITS_RE.findall(queuetypes)) - - -class ValidityOutputGenerator(OutputGenerator): - """ValidityOutputGenerator - subclass of OutputGenerator. - - Generates AsciiDoc includes of valid usage information, for reference - pages and the specification. Similar to DocOutputGenerator. - - ---- methods ---- - ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for - OutputGenerator. Defines additional internal state. - ---- methods overriding base class ---- - beginFile(genOpts) - endFile() - beginFeature(interface, emit) - endFeature() - genCmd(cmdinfo) - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.currentExtension = '' - - @property - def null(self): - """Preferred spelling of NULL. - - Delegates to the object implementing ConventionsBase. - """ - return self.conventions.null - - @property - def structtype_member_name(self): - """Return name of the structure type member. - - Delegates to the object implementing ConventionsBase. - """ - return self.conventions.structtype_member_name - - @property - def nextpointer_member_name(self): - """Return name of the structure pointer chain member. - - Delegates to the object implementing ConventionsBase. - """ - return self.conventions.nextpointer_member_name - - def makeProseList(self, elements, fmt=plf.AND, - comma_for_two_elts=False, *args, **kwargs): - """Make a (comma-separated) list for use in prose. - - Adds a connective (by default, 'and') - before the last element if there are more than 1. - - Optionally adds a quantifier (like 'any') before a list of 2 or more, - if specified by fmt. - - Delegates to the object implementing ConventionsBase. - """ - if not elements: - raise ValueError( - 'Cannot pass an empty list if you are trying to make a prose list.') - return self.conventions.makeProseList(elements, - fmt, - with_verb=False, - comma_for_two_elts=comma_for_two_elts, - *args, **kwargs) - - def makeProseListIs(self, elements, fmt=plf.AND, - comma_for_two_elts=False, *args, **kwargs): - """Make a (comma-separated) list for use in prose, followed by either 'is' or 'are' as appropriate. - - Adds a connective (by default, 'and') - before the last element if there are more than 1. - - Optionally adds a quantifier (like 'any') before a list of 2 or more, - if specified by fmt. - - Delegates to the object implementing ConventionsBase. - """ - if not elements: - raise ValueError( - 'Cannot pass an empty list if you are trying to make a prose list.') - return self.conventions.makeProseList(elements, - fmt, - with_verb=True, - comma_for_two_elts=comma_for_two_elts, - *args, **kwargs) - - def makeValidityCollection(self, entity_name): - """Create a ValidityCollection object, passing along our Conventions.""" - return ValidityCollection(entity_name, self.conventions) - - def beginFile(self, genOpts): - if not genOpts.conventions: - raise RuntimeError( - 'Must specify conventions object to generator options') - self.conventions = genOpts.conventions - # Vulkan says 'must: be a valid pointer' a lot, OpenXR just says - # 'must: be a pointer'. - self.valid_pointer_text = ' '.join( - (x for x in (self.conventions.valid_pointer_prefix, 'pointer') if x)) - OutputGenerator.beginFile(self, genOpts) - - def endFile(self): - OutputGenerator.endFile(self) - - def beginFeature(self, interface, emit): - # Start processing in superclass - OutputGenerator.beginFeature(self, interface, emit) - self.currentExtension = interface.get('name') - - def endFeature(self): - # Finish processing in superclass - OutputGenerator.endFeature(self) - - @property - def struct_macro(self): - """Get the appropriate format macro for a structure.""" - # delegate to conventions - return self.conventions.struct_macro - - def makeStructName(self, name): - """Prepend the appropriate format macro for a structure to a structure type name.""" - # delegate to conventions - return self.conventions.makeStructName(name) - - def makeParameterName(self, name): - """Prepend the appropriate format macro for a parameter/member to a parameter name.""" - return 'pname:' + name - - def makeBaseTypeName(self, name): - """Prepend the appropriate format macro for a 'base type' to a type name.""" - return 'basetype:' + name - - def makeEnumerationName(self, name): - """Prepend the appropriate format macro for an enumeration type to a enum type name.""" - return 'elink:' + name - - def makeFlagsName(self, name): - """Prepend the appropriate format macro for a flags type to a flags type name.""" - return 'tlink:' + name - - def makeFuncPointerName(self, name): - """Prepend the appropriate format macro for a function pointer type to a type name.""" - return 'tlink:' + name - - def makeExternalTypeName(self, name): - """Prepend the appropriate format macro for an external type like uint32_t to a type name.""" - # delegate to conventions - return self.conventions.makeExternalTypeName(name) - - def makeEnumerantName(self, name): - """Prepend the appropriate format macro for an enumerate (value) to a enum value name.""" - return 'ename:' + name - - def writeInclude(self, directory, basename, validity: ValidityCollection, - threadsafety, commandpropertiesentry=None, - successcodes=None, errorcodes=None): - """Generate an include file. - - directory - subdirectory to put file in (absolute or relative pathname) - basename - base name of the file - validity - ValidityCollection to write. - threadsafety - List (may be empty) of thread safety statements to write. - successcodes - Optional success codes to document. - errorcodes - Optional error codes to document. - """ - # Create subdirectory, if needed - directory = Path(directory) - if not directory.is_absolute(): - directory = Path(self.genOpts.directory) / directory - self.makeDir(str(directory)) - - # Create validity file - filename = str(directory / (basename + '.txt')) - self.logMsg('diag', '# Generating include file:', filename) - - with open(filename, 'w', encoding='utf-8') as fp: - write(self.conventions.warning_comment, file=fp) - - # Valid Usage - if validity: - write('.Valid Usage (Implicit)', file=fp) - write('****', file=fp) - write(validity, file=fp, end='') - write('****', file=fp) - write('', file=fp) - - # Host Synchronization - if threadsafety: - # The heading of this block differs between projects, so an Asciidoc attribute is used. - write('.{externsynctitle}', file=fp) - write('****', file=fp) - write(threadsafety, file=fp, end='') - write('****', file=fp) - write('', file=fp) - - # Command Properties - contained within a block, to avoid table numbering - if commandpropertiesentry: - write('.Command Properties', file=fp) - write('****', file=fp) - write('[options="header", width="100%"]', file=fp) - write('|====', file=fp) - write('|<<VkCommandBufferLevel,Command Buffer Levels>>|<<vkCmdBeginRenderPass,Render Pass Scope>>|<<VkQueueFlagBits,Supported Queue Types>>', file=fp) - write(commandpropertiesentry, file=fp) - write('|====', file=fp) - write('****', file=fp) - write('', file=fp) - - # Success Codes - contained within a block, to avoid table numbering - if successcodes or errorcodes: - write('.Return Codes', file=fp) - write('****', file=fp) - if successcodes: - write('ifndef::doctype-manpage[]', file=fp) - write('<<fundamentals-successcodes,Success>>::', file=fp) - write('endif::doctype-manpage[]', file=fp) - write('ifdef::doctype-manpage[]', file=fp) - write('On success, this command returns::', file=fp) - write('endif::doctype-manpage[]', file=fp) - write(successcodes, file=fp) - if errorcodes: - write('ifndef::doctype-manpage[]', file=fp) - write('<<fundamentals-errorcodes,Failure>>::', file=fp) - write('endif::doctype-manpage[]', file=fp) - write('ifdef::doctype-manpage[]', file=fp) - write('On failure, this command returns::', file=fp) - write('endif::doctype-manpage[]', file=fp) - write(errorcodes, file=fp) - write('****', file=fp) - write('', file=fp) - - def paramIsPointer(self, param): - """Check if the parameter passed in is a pointer.""" - tail = param.find('type').tail - return tail is not None and '*' in tail - - def paramIsStaticArray(self, param): - """Check if the parameter passed in is a static array.""" - tail = param.find('name').tail - return tail and tail[0] == '[' - - def paramIsConst(self, param): - """Check if the parameter passed in has a type that mentions const.""" - return param.text is not None and 'const' in param.text - - def staticArrayLength(self, param): - """Get the length of a parameter that's been identified as a static array.""" - paramenumsize = param.find('enum') - if paramenumsize is not None: - return paramenumsize.text - # TODO switch to below when cosmetic changes OK - # return self.makeEnumerantName(paramenumsize.text) - - return param.find('name').tail[1:-1] - - def paramIsArray(self, param): - """Check if the parameter passed in is a pointer to an array.""" - return param.get('len') is not None - - def getHandleDispatchableAncestors(self, typename): - """Get the ancestors of a handle object.""" - ancestors = [] - current = typename - while True: - current = self.getHandleParent(current) - if current is None: - return ancestors - if self.isHandleTypeDispatchable(current): - ancestors.append(current) - - def isHandleTypeDispatchable(self, handlename): - """Check if a parent object is dispatchable or not.""" - handle = self.registry.tree.find( - "types/type/[name='" + handlename + "'][@category='handle']") - if handle is not None and getElemType(handle) == 'VK_DEFINE_HANDLE': - return True - else: - return False - - def isHandleOptional(self, param, params): - # Simple, if it's optional, return true - if param.get('optional') is not None: - return True - - # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes. - if param.get('noautovalidity') is not None: - return True - - # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional - if self.paramIsArray(param): - for length in LengthEntry.parse_len_from_param(param): - if not length.other_param_name: - # don't care about constants or "null-terminated" - continue - - other_param = findNamedElem(params, length.other_param_name) - if other_param is None: - self.logMsg('warn', length.other_param_name, - 'is listed as a length for parameter', param, 'but no such parameter exists') - if other_param and other_param.get('optional'): - return True - - return False - - def makeOptionalPre(self, param): - # Don't generate this stub for bitflags - param_name = getElemName(param) - paramtype = getElemType(param) - type_category = self.getTypeCategory(paramtype) - is_optional = param.get('optional').split(',')[0] == 'true' - if type_category != 'bitmask' and is_optional: - if self.paramIsArray(param) or self.paramIsPointer(param): - optional_val = self.null - elif type_category == 'handle': - if self.isHandleTypeDispatchable(paramtype): - optional_val = self.null - else: - optional_val = 'dlink:' + self.conventions.api_prefix + 'NULL_HANDLE' - else: - optional_val = self.conventions.zero - return 'If {} is not {}, '.format( - self.makeParameterName(param_name), - optional_val) - - return "" - - def makeParamValidityPre(self, param, params, selector): - """Make the start of an entry for a parameter's validity, including a chunk of text if it is an array.""" - param_name = getElemName(param) - paramtype = getElemType(param) - - # General pre-amble. Check optionality and add stuff. - entry = ValidityEntry(anchor=(param_name, 'parameter')) - is_optional = param.get('optional') is not None and param.get('optional').split(',')[0] == 'true' - - # This is for a union member, and the valid member is chosen by an enum selection - if selector: - selection = param.get('selection') - - entry += 'If {} is {}, '.format( - self.makeParameterName(selector), - self.makeEnumerantName(selection)) - - if is_optional: - entry += "and " - optionalpre = self.makeOptionalPre(param) - entry += optionalpre[0].lower() + optionalpre[1:] - - return entry - - if self.paramIsStaticArray(param): - if paramtype != 'char': - entry += 'Any given element of ' - return entry - - if self.paramIsArray(param) and param.get('len') != LengthEntry.NULL_TERMINATED_STRING: - # Find all the parameters that are called out as optional, - # so we can document that they might be zero, and the array may be ignored - optionallengths = [] - for length in LengthEntry.parse_len_from_param(param): - if not length.other_param_name: - # Only care about length entries that are parameter names - continue - - other_param = findNamedElem(params, length.other_param_name) - other_param_optional = (other_param is not None) and ( - other_param.get('optional') is not None) - - if other_param is None or not other_param_optional: - # Don't care about not-found params or non-optional params - continue - - if self.paramIsPointer(other_param): - optionallengths.append( - 'the value referenced by ' + self.makeParameterName(length.other_param_name)) - else: - optionallengths.append( - self.makeParameterName(length.other_param_name)) - - # Document that these arrays may be ignored if any of the length values are 0 - if optionallengths or is_optional: - entry += 'If ' - if optionallengths: - entry += self.makeProseListIs(optionallengths, fmt=plf.OR) - entry += ' not %s, ' % self.conventions.zero - # TODO enabling this in OpenXR, as used in Vulkan, causes nonsensical things like - # "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" - if optionallengths and is_optional: - entry += 'and ' - if is_optional: - entry += self.makeParameterName(param_name) - # TODO switch when cosmetic changes OK - # entry += ' is not {}, '.format(self.null) - entry += ' is not `NULL`, ' - return entry - - if param.get('optional'): - entry += self.makeOptionalPre(param) - return entry - - # If none of the early returns happened, we at least return an empty - # entry with an anchor. - return entry - - def createValidationLineForParameterImpl(self, blockname, param, params, typetext, selector, parentname): - """Make the generic validity portion used for all parameters. - - May return None if nothing to validate. - """ - if param.get('noautovalidity') is not None: - return None - - validity = self.makeValidityCollection(blockname) - param_name = getElemName(param) - paramtype = getElemType(param) - - entry = self.makeParamValidityPre(param, params, selector) - - # This is for a child member of a union - if selector: - entry += 'the {} member of {} must: be '.format(self.makeParameterName(param_name), self.makeParameterName(parentname)) - else: - entry += '{} must: be '.format(self.makeParameterName(param_name)) - - if self.paramIsStaticArray(param) and paramtype == 'char': - # TODO this is a minor hack to determine if this is a command parameter or a struct member - if self.paramIsConst(param) or blockname.startswith(self.conventions.type_prefix): - entry += 'a null-terminated UTF-8 string whose length is less than or equal to ' - entry += self.staticArrayLength(param) - else: - # This is a command's output parameter - entry += 'a character array of length %s ' % self.staticArrayLength(param) - validity += entry - return validity - - elif self.paramIsArray(param): - # Arrays. These are hard to get right, apparently - - lengths = LengthEntry.parse_len_from_param(param) - - for i, length in enumerate(LengthEntry.parse_len_from_param(param)): - if i == 0: - # If the first index, make it singular. - entry += 'a ' - array_text = 'an array' - pointer_text = self.valid_pointer_text - else: - array_text = 'arrays' - pointer_text = self.valid_pointer_text + 's' - - if length.null_terminated: - # This should always be the last thing. - # If it ever isn't for some bizarre reason, then this will need some massaging. - entry += 'null-terminated ' - elif length.number == 1: - entry += pointer_text - entry += ' to ' - else: - entry += pointer_text - entry += ' to ' - entry += array_text - entry += ' of ' - # Handle equations, which are currently denoted with latex - if length.math: - # Handle equations, which are currently denoted with latex - entry += str(length) - else: - entry += self.makeParameterName(str(length)) - entry += ' ' - - # Void pointers don't actually point at anything - remove the word "to" - if paramtype == 'void': - if lengths[-1].number == 1: - if len(lengths) > 1: - # Take care of the extra s added by the post array chunk function. #HACK# - entry.drop_end(5) - else: - entry.drop_end(4) - - # This hasn't been hit, so this hasn't been tested recently. - raise UnhandledCaseError( - "Got void pointer param/member with last length 1") - else: - # An array of void values is a byte array. - entry += 'byte' - - elif paramtype == 'char': - # A null terminated array of chars is a string - if lengths[-1].null_terminated: - entry += 'UTF-8 string' - else: - # Else it's just a bunch of chars - entry += 'char value' - - elif self.paramIsConst(param): - # If a value is "const" that means it won't get modified, so it must be valid going into the function. - if 'const' in param.text: - - if not self.isStructAlwaysValid(paramtype): - entry += 'valid ' - - # Check if the array elements are optional - array_element_optional = param.get('optional') is not None \ - and len(param.get('optional').split(',')) == len(LengthEntry.parse_len_from_param(param)) + 1 \ - and param.get('optional').split(',')[-1] == 'true' - if array_element_optional and self.getTypeCategory(paramtype) != 'bitmask': # bitmask is handled later - entry += 'or dlink:' + self.conventions.api_prefix + 'NULL_HANDLE ' - - entry += typetext - - # pluralize - if len(lengths) > 1 or (lengths[0] != 1 and not lengths[0].null_terminated): - entry += 's' - - return self.handleRequiredBitmask(blockname, param, paramtype, entry, 'true' if array_element_optional else None) - - if self.paramIsPointer(param): - # Handle pointers - which are really special case arrays (i.e. they don't have a length) - # TODO should do something here if someone ever uses some intricate comma-separated `optional` - pointercount = param.find('type').tail.count('*') - - # Treat void* as an int - if paramtype == 'void': - optional = param.get('optional') - # If there is only void*, it is just optional int - we don't need any language. - if pointercount == 1 and optional is not None: - return None # early return - # Treat the inner-most void* as an int - pointercount -= 1 - - # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that. - entry += 'a ' - entry += (self.valid_pointer_text + ' to a ') * pointercount - - # Handle void* and pointers to it - if paramtype == 'void': - if optional is None or optional.split(',')[pointercount]: - # The last void* is just optional int (e.g. to be filled by the impl.) - typetext = 'pointer value' - - # If a value is "const" that means it won't get modified, so it must be valid going into the function. - elif self.paramIsConst(param) and paramtype != 'void': - entry += 'valid ' - - entry += typetext - return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional')) - - # Add additional line for non-optional bitmasks - if self.getTypeCategory(paramtype) == 'bitmask': - # TODO does not really handle if someone tries something like optional="true,false" - # TODO OpenXR has 0 or a valid combination of flags, for optional things. - # Vulkan doesn't... - # isMandatory = param.get('optional') is None - # if not isMandatory: - # entry += self.conventions.zero - # entry += ' or ' - # Non-pointer, non-optional things must be valid - entry += 'a valid {}'.format(typetext) - - return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional')) - - # Non-pointer, non-optional things must be valid - entry += 'a valid {}'.format(typetext) - return entry - - def handleRequiredBitmask(self, blockname, param, paramtype, entry, optional): - # TODO does not really handle if someone tries something like optional="true,false" - if self.getTypeCategory(paramtype) != 'bitmask' or optional == 'true': - return entry - if self.paramIsPointer(param) and not self.paramIsArray(param): - # This is presumably an output parameter - return entry - - param_name = getElemName(param) - # If mandatory, then we need two entries instead of just one. - validity = self.makeValidityCollection(blockname) - validity += entry - - entry2 = ValidityEntry(anchor=(param_name, 'requiredbitmask')) - if self.paramIsArray(param): - entry2 += 'Each element of ' - entry2 += '{} must: not be {}'.format( - self.makeParameterName(param_name), self.conventions.zero) - validity += entry2 - return validity - - def createValidationLineForParameter(self, blockname, param, params, typecategory, selector, parentname): - """Make an entire validation entry for a given parameter.""" - param_name = getElemName(param) - paramtype = getElemType(param) - - is_array = self.paramIsArray(param) - is_pointer = self.paramIsPointer(param) - needs_recursive_validity = (is_array - or is_pointer - or not self.isStructAlwaysValid(paramtype)) - typetext = None - if paramtype in ('void', 'char'): - # Chars and void are special cases - we call the impl function, - # but don't use the typetext. - # A null-terminated char array is a string, else it's chars. - # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular - typetext = '' - - elif typecategory == 'bitmask': - bitsname = paramtype.replace('Flags', 'FlagBits') - bitselem = self.registry.tree.find("enums[@name='" + bitsname + "']") - - # If bitsname is an alias, then use the alias to get bitselem. - typeElem = self.registry.lookupElementInfo(bitsname, self.registry.typedict) - if typeElem is not None: - alias = self.registry.getAlias(typeElem.elem, self.registry.typedict) - if alias is not None: - bitselem = self.registry.tree.find("enums[@name='" + alias + "']") - - if bitselem is None or len(bitselem.findall('enum[@required="true"]')) == 0: - # Empty bit mask: presumably just a placeholder (or only in - # an extension not enabled for this build) - entry = ValidityEntry( - anchor=(param_name, 'zerobitmask')) - entry += self.makeParameterName(param_name) - entry += ' must: be ' - entry += self.conventions.zero - # Early return - return entry - - is_const = self.paramIsConst(param) - - if is_array: - if is_const: - # input an array of bitmask values - template = 'combinations of {bitsname} value' - else: - template = '{paramtype} value' - elif is_pointer: - if is_const: - template = 'combination of {bitsname} values' - else: - template = '{paramtype} value' - else: - template = 'combination of {bitsname} values' - - # The above few cases all use makeEnumerationName, just with different context. - typetext = template.format( - bitsname=self.makeEnumerationName(bitsname), - paramtype=self.makeFlagsName(paramtype)) - - elif typecategory == 'handle': - typetext = '{} handle'.format(self.makeStructName(paramtype)) - - elif typecategory == 'enum': - typetext = '{} value'.format(self.makeEnumerationName(paramtype)) - - elif typecategory == 'funcpointer': - typetext = '{} value'.format(self.makeFuncPointerName(paramtype)) - - elif typecategory == 'struct': - if needs_recursive_validity: - typetext = '{} structure'.format( - self.makeStructName(paramtype)) - - elif typecategory == 'union': - if needs_recursive_validity: - typetext = '{} union'.format(self.makeStructName(paramtype)) - - elif self.paramIsArray(param) or self.paramIsPointer(param): - # TODO sync cosmetic changes from OpenXR? - typetext = '{} value'.format(self.makeBaseTypeName(paramtype)) - - elif typecategory is None: - if not self.isStructAlwaysValid(paramtype): - typetext = '{} value'.format( - self.makeExternalTypeName(paramtype)) - - # "a valid uint32_t value" doesn't make much sense. - pass - - # If any of the above conditions matched and set typetext, - # we call using it. - if typetext is not None: - return self.createValidationLineForParameterImpl( - blockname, param, params, typetext, selector, parentname) - return None - - def makeHandleValidityParent(self, param, params): - """Make a validity entry for a handle's parent object. - - Creates 'parent' VUID. - """ - param_name = getElemName(param) - paramtype = getElemType(param) - - # Deal with handle parents - handleparent = self.getHandleParent(paramtype) - if handleparent is None: - return None - - otherparam = findTypedElem(params, handleparent) - if otherparam is None: - return None - - parent_name = getElemName(otherparam) - entry = ValidityEntry(anchor=(param_name, 'parent')) - - is_optional = self.isHandleOptional(param, params) - - if self.paramIsArray(param): - template = 'Each element of {}' - if is_optional: - template += ' that is a valid handle' - elif is_optional: - template = 'If {} is a valid handle, it' - else: - # not optional, not an array. Just say the parameter name. - template = '{}' - - entry += template.format(self.makeParameterName(param_name)) - - entry += ' must: have been created, allocated, or retrieved from {}'.format( - self.makeParameterName(parent_name)) - - return entry - - def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params): - """Make an asciidoc validity entry for a common ancestors between handles. - - Only handles parent validity for signatures taking multiple handles - any ancestors also being supplied to this function. - (e.g. "Each of x, y, and z must: come from the same slink:ParentHandle") - See self.makeAsciiDocHandleParent() for instances where the parent - handle is named and also passed. - - Creates 'commonparent' VUID. - """ - # TODO Replace with refactored code from OpenXR - entry = None - - if len(handles) > 1: - ancestormap = {} - anyoptional = False - # Find all the ancestors - for param in handles: - paramtype = getElemType(param) - - if not self.paramIsPointer(param) or (param.text and 'const' in param.text): - ancestors = self.getHandleDispatchableAncestors(paramtype) - - ancestormap[param] = ancestors - - anyoptional |= self.isHandleOptional(param, params) - - # Remove redundant ancestor lists - for param in handles: - paramtype = getElemType(param) - - removals = [] - for ancestors in ancestormap.items(): - if paramtype in ancestors[1]: - removals.append(ancestors[0]) - - if removals != []: - for removal in removals: - del(ancestormap[removal]) - - # Intersect - - if len(ancestormap.values()) > 1: - current = list(ancestormap.values())[0] - for ancestors in list(ancestormap.values())[1:]: - current = [val for val in current if val in ancestors] - - if len(current) > 0: - commonancestor = current[0] - - if len(ancestormap.keys()) > 1: - - entry = ValidityEntry(anchor=('commonparent',)) - - parametertexts = [] - for param in ancestormap.keys(): - param_name = getElemName(param) - parametertext = self.makeParameterName(param_name) - if self.paramIsArray(param): - parametertext = 'the elements of ' + parametertext - parametertexts.append(parametertext) - - parametertexts.sort() - - if len(parametertexts) > 2: - entry += 'Each of ' - else: - entry += 'Both of ' - - entry += self.makeProseList(parametertexts, - comma_for_two_elts=True) - if anyoptional is True: - entry += ' that are valid handles of non-ignored parameters' - entry += ' must: have been created, allocated, or retrieved from the same ' - entry += self.makeStructName(commonancestor) - - return entry - - def makeStructureTypeFromName(self, structname): - """Create text for a structure type name, like ename:VK_STRUCTURE_TYPE_CREATE_INSTANCE_INFO""" - return self.makeEnumerantName(self.conventions.generate_structure_type_from_name(structname)) - - def makeStructureTypeValidity(self, structname): - """Generate an validity line for the type value of a struct. - - Creates VUID named like the member name. - """ - info = self.registry.typedict.get(structname) - assert(info is not None) - - # If this fails (meaning we have something other than a struct in here), - # then the caller is wrong: - # probably passing the wrong value for structname. - members = info.getMembers() - assert(members) - - # If this fails, see caller: this should only get called for a struct type with a type value. - param = findNamedElem(members, self.structtype_member_name) - # OpenXR gets some structs without a type field in here, so can't assert - assert(param is not None) - # if param is None: - # return None - - entry = ValidityEntry( - anchor=(self.structtype_member_name, self.structtype_member_name)) - entry += self.makeParameterName(self.structtype_member_name) - entry += ' must: be ' - - values = param.get('values', '').split(',') - if values: - # Extract each enumerant value. They could be validated in the - # same fashion as validextensionstructs in - # makeStructureExtensionPointer, although that's not relevant in - # the current extension struct model. - entry += self.makeProseList((self.makeEnumerantName(v) - for v in values), 'or') - return entry - - if 'Base' in structname: - # This type doesn't even have any values for its type, - # and it seems like it might be a base struct that we'd expect to lack its own type, - # so omit the entire statement - return None - - self.logMsg('warn', 'No values were marked-up for the structure type member of', - structname, 'so making one up!') - entry += self.makeStructureTypeFromName(structname) - - return entry - - def makeStructureExtensionPointer(self, blockname, param): - """Generate an validity line for the pointer chain member value of a struct.""" - param_name = getElemName(param) - - if param.get('validextensionstructs') is not None: - self.logMsg('warn', blockname, - 'validextensionstructs is deprecated/removed', '\n') - - entry = ValidityEntry( - anchor=(param_name, self.nextpointer_member_name)) - validextensionstructs = self.registry.validextensionstructs.get( - blockname) - extensionstructs = [] - duplicatestructs = [] - - if validextensionstructs is not None: - # Check each structure name and skip it if not required by the - # generator. This allows tagging extension structs in the XML - # that are only included in validity when needed for the spec - # being targeted. - # Track the required structures, and of the required structures, - # those that allow duplicates in the pNext chain. - for struct in validextensionstructs: - # Unpleasantly breaks encapsulation. Should be a method in the registry class - t = self.registry.lookupElementInfo( - struct, self.registry.typedict) - if t is None: - self.logMsg('warn', 'makeStructureExtensionPointer: struct', struct, - 'is in a validextensionstructs= attribute but is not in the registry') - elif t.required: - extensionstructs.append('slink:' + struct) - if t.elem.get('allowduplicate') == 'true': - duplicatestructs.append('slink:' + struct) - else: - self.logMsg( - 'diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required') - - if not extensionstructs: - entry += '{} must: be {}'.format( - self.makeParameterName(param_name), self.null) - return entry - - if len(extensionstructs) == 1: - entry += '{} must: be {} or a pointer to a valid instance of {}'.format(self.makeParameterName(param_name), self.null, - extensionstructs[0]) - else: - # More than one extension struct. - entry += 'Each {} member of any structure (including this one) in the pname:{} chain '.format( - self.makeParameterName(param_name), self.nextpointer_member_name) - entry += 'must: be either {} or a pointer to a valid instance of '.format( - self.null) - - entry += self.makeProseList(extensionstructs, fmt=plf.OR) - - validity = self.makeValidityCollection(blockname) - validity += entry - - # Generate VU statement requiring unique structures in the pNext - # chain. - # NOTE: OpenXR always allows non-unique type values. Instances other - # than the first are just ignored - - vu = ('The pname:' + - self.structtype_member_name + - ' value of each struct in the pname:' + - self.nextpointer_member_name + - ' chain must: be unique') - anchor = (self.conventions.member_used_for_unique_vuid, 'unique') - - # If duplicates of some structures are allowed, they are called out - # explicitly. - num = len(duplicatestructs) - if num > 0: - vu = (vu + - ', with the exception of structures of type ' + - self.makeProseList(duplicatestructs, fmt=plf.OR)) - - validity.addValidityEntry(vu, anchor = anchor ) - - return validity - - def addSharedStructMemberValidity(self, struct, blockname, param, validity): - """Generate language to independently validate a parameter, for those validated even in output. - - Return value indicates whether it was handled internally (True) or if it may need more validity (False).""" - param_name = getElemName(param) - paramtype = getElemType(param) - if param.get('noautovalidity') is None: - - if self.conventions.is_structure_type_member(paramtype, param_name): - validity += self.makeStructureTypeValidity(blockname) - return True - - if self.conventions.is_nextpointer_member(paramtype, param_name): - # Vulkan: the addition of validity here is conditional unlike OpenXR. - if struct.get('structextends') is None: - validity += self.makeStructureExtensionPointer( - blockname, param) - return True - return False - - def makeOutputOnlyStructValidity(self, cmd, blockname, params): - """Generate all the valid usage information for a struct that's entirely output. - - That is, it is only ever filled out by the implementation other than - the structure type and pointer chain members. - Thus, we only create validity for the pointer chain member. - """ - # Start the validity collection for this struct - validity = self.makeValidityCollection(blockname) - - for param in params: - self.addSharedStructMemberValidity( - cmd, blockname, param, validity) - - return validity - - def isVKVersion11(self): - """Returns true if VK_VERSION_1_1 is being emitted.""" - vk11 = re.match(self.registry.genOpts.emitversions, 'VK_VERSION_1_1') is not None - return vk11 - - def makeStructOrCommandValidity(self, cmd, blockname, params): - """Generate all the valid usage information for a given struct or command.""" - validity = self.makeValidityCollection(blockname) - handles = [] - arraylengths = dict() - for param in params: - param_name = getElemName(param) - paramtype = getElemType(param) - - # Valid usage ID tags (VUID) are generated for various - # conditions based on the name of the block (structure or - # command), name of the element (member or parameter), and type - # of VU statement. - - # Get the type's category - typecategory = self.getTypeCategory(paramtype) - - if not self.addSharedStructMemberValidity( - cmd, blockname, param, validity): - if not param.get('selector'): - validity += self.createValidationLineForParameter( - blockname, param, params, typecategory, None, None) - else: - selector = param.get('selector') - if typecategory != 'union': - self.logMsg('warn', 'selector attribute set on non-union parameter', param_name, 'in', blockname) - - paraminfo = self.registry.lookupElementInfo(paramtype, self.registry.typedict) - - for member in paraminfo.getMembers(): - membertype = getElemType(member) - membertypecategory = self.getTypeCategory(membertype) - - validity += self.createValidationLineForParameter( - blockname, member, paraminfo.getMembers(), membertypecategory, selector, param_name) - - # Ensure that any parenting is properly validated, and list that a handle was found - if typecategory == 'handle': - handles.append(param) - - # Get the array length for this parameter - lengths = LengthEntry.parse_len_from_param(param) - if lengths: - arraylengths.update({length.other_param_name: length - for length in lengths - if length.other_param_name}) - - # For any vkQueue* functions, there might be queue type data - if 'vkQueue' in blockname: - # The queue type must be valid - queuebits = _parse_queue_bits(cmd) - if queuebits: - entry = ValidityEntry(anchor=('queuetype',)) - entry += 'The pname:queue must: support ' - entry += self.makeProseList(queuebits, - fmt=plf.OR, comma_for_two_elts=True) - entry += ' operations' - validity += entry - - if 'vkCmd' in blockname: - # The commandBuffer parameter must be being recorded - entry = ValidityEntry(anchor=('commandBuffer', 'recording')) - entry += 'pname:commandBuffer must: be in the <<commandbuffers-lifecycle, recording state>>' - validity += entry - - # - # Start of valid queue type validation - command pool must have been - # allocated against a queue with at least one of the valid queue types - entry = ValidityEntry(anchor=('commandBuffer', 'cmdpool')) - - # - # This test for vkCmdFillBuffer is a hack, since we have no path - # to conditionally have queues enabled or disabled by an extension. - # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now - if blockname == 'vkCmdFillBuffer': - entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support ' - if self.isVKVersion11() or 'VK_KHR_maintenance1' in self.registry.requiredextensions: - entry += 'transfer, graphics or compute operations' - else: - entry += 'graphics or compute operations' - else: - # The queue type must be valid - queuebits = _parse_queue_bits(cmd) - assert(queuebits) - entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support ' - entry += self.makeProseList(queuebits, - fmt=plf.OR, comma_for_two_elts=True) - entry += ' operations' - validity += entry - - # Must be called inside/outside a render pass appropriately - renderpass = cmd.get('renderpass') - - if renderpass != 'both': - entry = ValidityEntry(anchor=('renderpass',)) - entry += 'This command must: only be called ' - entry += renderpass - entry += ' of a render pass instance' - validity += entry - - # Must be in the right level command buffer - cmdbufferlevel = cmd.get('cmdbufferlevel') - - if cmdbufferlevel != 'primary,secondary': - entry = ValidityEntry(anchor=('bufferlevel',)) - entry += 'pname:commandBuffer must: be a ' - entry += cmdbufferlevel - entry += ' sname:VkCommandBuffer' - validity += entry - - # Any non-optional arraylengths should specify they must be greater than 0 - array_length_params = ((param, getElemName(param)) - for param in params - if getElemName(param) in arraylengths) - - for param, param_name in array_length_params: - if param.get('optional') is not None: - continue - - length = arraylengths[param_name] - full_length = length.full_reference - - # Is this just a name of a param? If false, then it's some kind of qualified name (a member of a param for instance) - simple_param_reference = (len(length.param_ref_parts) == 1) - if not simple_param_reference: - # Loop through to see if any parameters in the chain are optional - array_length_parent = cmd - array_length_optional = False - for part in length.param_ref_parts: - # Overwrite the param so it ends up as the bottom level parameter for later checks - param = array_length_parent.find("*/[name='{}']".format(part)) - - # If any parameter in the chain is optional, skip the implicit length requirement - array_length_optional |= (param.get('optional') is not None) - - # Lookup the type of the parameter for the next loop iteration - type = param.findtext('type') - array_length_parent = self.registry.tree.find("./types/type/[@name='{}']".format(type)) - - if array_length_optional: - continue - - # Get all the array dependencies - arrays = cmd.findall( - "param/[@len='{}'][@optional='true']".format(full_length)) - - # Get all the optional array dependencies, including those not generating validity for some reason - optionalarrays = arrays + \ - cmd.findall( - "param/[@len='{}'][@noautovalidity='true']".format(full_length)) - - entry = ValidityEntry(anchor=(full_length, 'arraylength')) - # Allow lengths to be arbitrary if all their dependents are optional - if optionalarrays and len(optionalarrays) == len(arrays): - entry += 'If ' - # TODO sync this section from OpenXR once cosmetic changes OK - - optional_array_names = (self.makeParameterName(getElemName(array)) - for array in optionalarrays) - entry += self.makeProseListIs(optional_array_names, - plf.ANY_OR, comma_for_two_elts=True) - - entry += ' not {}, '.format(self.null) - - # TODO end needs sync cosmetic - if self.paramIsPointer(param): - entry += 'the value referenced by ' - - # Split and re-join here to insert pname: around :: - entry += '::'.join(self.makeParameterName(part) - for part in full_length.split('::')) - # TODO replace the previous statement with the following when cosmetic changes OK - # entry += length.get_human_readable(make_param_name=self.makeParameterName) - - entry += ' must: be greater than ' - entry += self.conventions.zero - validity += entry - - # Find the parents of all objects referenced in this command - for param in handles: - # Don't detect a parent for return values! - if not self.paramIsPointer(param) or self.paramIsConst(param): - validity += self.makeHandleValidityParent(param, params) - - # Find the common ancestor of all objects referenced in this command - validity += self.makeAsciiDocHandlesCommonAncestor( - blockname, handles, params) - - return validity - - def makeThreadSafetyBlock(self, cmd, paramtext): - """Generate thread-safety validity entries for cmd/structure""" - # See also makeThreadSafetyBlock in validitygenerator.py - validity = self.makeValidityCollection(getElemName(cmd)) - - # This text varies between projects, so an Asciidoctor attribute is used. - extsync_prefix = "{externsyncprefix} " - - # Find and add any parameters that are thread unsafe - explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]") - if explicitexternsyncparams is not None: - for param in explicitexternsyncparams: - externsyncattribs = ExternSyncEntry.parse_externsync_from_param( - param) - param_name = getElemName(param) - - for attrib in externsyncattribs: - entry = ValidityEntry() - entry += extsync_prefix - if attrib.entirely_extern_sync: - if self.paramIsArray(param): - entry += 'each member of ' - elif self.paramIsPointer(param): - entry += 'the object referenced by ' - - entry += self.makeParameterName(param_name) - - if attrib.children_extern_sync: - entry += ', and any child handles,' - - else: - entry += 'pname:' - entry += str(attrib.full_reference) - # TODO switch to the following when cosmetic changes OK - # entry += attrib.get_human_readable(make_param_name=self.makeParameterName) - entry += ' must: be externally synchronized' - validity += entry - - # Vulkan-specific - # For any vkCmd* functions, the command pool is externally synchronized - if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name').text: - entry = ValidityEntry() - entry += extsync_prefix - entry += 'the sname:VkCommandPool that pname:commandBuffer was allocated from must: be externally synchronized' - validity += entry - - # Find and add any "implicit" parameters that are thread unsafe - implicitexternsyncparams = cmd.find('implicitexternsyncparams') - if implicitexternsyncparams is not None: - for elem in implicitexternsyncparams: - entry = ValidityEntry() - entry += extsync_prefix - entry += elem.text - entry += ' must: be externally synchronized' - validity += entry - - return validity - - def makeCommandPropertiesTableEntry(self, cmd, name): - - if 'vkCmd' in name: - # Must be called inside/outside a render pass appropriately - cmdbufferlevel = cmd.get('cmdbufferlevel') - cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(',')) - - renderpass = cmd.get('renderpass') - renderpass = renderpass.capitalize() - - # - # This test for vkCmdFillBuffer is a hack, since we have no path - # to conditionally have queues enabled or disabled by an extension. - # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now - if name == 'vkCmdFillBuffer': - if self.isVKVersion11() or 'VK_KHR_maintenance1' in self.registry.requiredextensions: - queues = 'Transfer + \nGraphics + \nCompute' - else: - queues = 'Graphics + \nCompute' - else: - queues = cmd.get('queues') - queues = (' + \n').join(queues.title().split(',')) - - return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues - elif 'vkQueue' in name: - # Must be called inside/outside a render pass appropriately - - queues = cmd.get('queues') - if queues is None: - queues = 'Any' - else: - queues = (' + \n').join(queues.upper().split(',')) - - return '|-|-|' + queues - - return None - - - def findRequiredEnums(self, enums): - """Check each enumerant name in the enums list and remove it if not - required by the generator. This allows specifying success and error - codes for extensions that are only included in validity when needed - for the spec being targeted.""" - return self.keepOnlyRequired(enums, self.registry.enumdict) - - def findRequiredCommands(self, commands): - """Check each command name in the commands list and remove it if not - required by the generator. - - This will allow some state operations to take place before endFile.""" - return self.keepOnlyRequired(commands, self.registry.cmddict) - - def keepOnlyRequired(self, names, info_dict): - """Check each element name in the supplied dictionary and remove it if not - required by the generator. - - This will allow some operations to take place before endFile no matter the order of generation.""" - # TODO Unpleasantly breaks encapsulation. Should be a method in the registry class - - def is_required(name): - info = self.registry.lookupElementInfo(name, info_dict) - if info is None: - return False - if not info.required: - self.logMsg('diag', 'keepOnlyRequired: element', - name, 'IS NOT required, skipping') - return info.required - - return [name - for name in names - if is_required(name)] - - def makeReturnCodeList(self, attrib, cmd, name): - """Return a list of possible return codes for a function. - - attrib is either 'successcodes' or 'errorcodes'. - """ - return_lines = [] - RETURN_CODE_FORMAT = '* ename:{}' - - codes_attr = cmd.get(attrib) - if codes_attr: - codes = self.findRequiredEnums(codes_attr.split(',')) - if codes: - return_lines.extend((RETURN_CODE_FORMAT.format(code) - for code in codes)) - - applicable_ext_codes = (ext_code - for ext_code in self.registry.commandextensionsuccesses - if ext_code.command == name) - for ext_code in applicable_ext_codes: - line = RETURN_CODE_FORMAT.format(ext_code.value) - if ext_code.extension: - line += ' [only if {} is enabled]'.format( - self.conventions.formatExtension(ext_code.extension)) - - return_lines.append(line) - if return_lines: - return '\n'.join(return_lines) - - return None - - def makeSuccessCodes(self, cmd, name): - return self.makeReturnCodeList('successcodes', cmd, name) - - def makeErrorCodes(self, cmd, name): - return self.makeReturnCodeList('errorcodes', cmd, name) - - def genCmd(self, cmdinfo, name, alias): - """Command generation.""" - OutputGenerator.genCmd(self, cmdinfo, name, alias) - - # @@@ (Jon) something needs to be done here to handle aliases, probably - - validity = self.makeValidityCollection(name) - - # OpenXR-only: make sure extension is enabled - # validity.possiblyAddExtensionRequirement(self.currentExtension, 'calling flink:') - - validity += self.makeStructOrCommandValidity( - cmdinfo.elem, name, cmdinfo.getParams()) - - threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param') - commandpropertiesentry = None - - # Vulkan-specific - commandpropertiesentry = self.makeCommandPropertiesTableEntry( - cmdinfo.elem, name) - successcodes = self.makeSuccessCodes(cmdinfo.elem, name) - errorcodes = self.makeErrorCodes(cmdinfo.elem, name) - - # OpenXR-specific - # self.generateStateValidity(validity, name) - - self.writeInclude('protos', name, validity, threadsafety, - commandpropertiesentry, successcodes, errorcodes) - - def genStruct(self, typeinfo, typeName, alias): - """Struct Generation.""" - OutputGenerator.genStruct(self, typeinfo, typeName, alias) - - # @@@ (Jon) something needs to be done here to handle aliases, probably - - # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information. - validity = self.makeValidityCollection(typeName) - threadsafety = [] - - # OpenXR-only: make sure extension is enabled - # validity.possiblyAddExtensionRequirement(self.currentExtension, 'using slink:') - - if typeinfo.elem.get('category') != 'union': - if typeinfo.elem.get('returnedonly') is None: - validity += self.makeStructOrCommandValidity( - typeinfo.elem, typeName, typeinfo.getMembers()) - threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member') - - else: - # Need to generate structure type and next pointer chain member validation - validity += self.makeOutputOnlyStructValidity( - typeinfo.elem, typeName, typeinfo.getMembers()) - - self.writeInclude('structs', typeName, validity, - threadsafety, None, None, None) - - def genGroup(self, groupinfo, groupName, alias): - """Group (e.g. C "enum" type) generation. - For the validity generator, this just tags individual enumerants - as required or not. - """ - OutputGenerator.genGroup(self, groupinfo, groupName, alias) - - # @@@ (Jon) something needs to be done here to handle aliases, probably - - groupElem = groupinfo.elem - - # Loop over the nested 'enum' tags. Keep track of the minimum and - # maximum numeric values, if they can be determined; but only for - # core API enumerants, not extension enumerants. This is inferred - # by looking for 'extends' attributes. - for elem in groupElem.findall('enum'): - name = elem.get('name') - ei = self.registry.lookupElementInfo(name, self.registry.enumdict) - - # Tag enumerant as required or not - ei.required = self.isEnumRequired(elem) - - def genType(self, typeinfo, name, alias): - """Type Generation.""" - OutputGenerator.genType(self, typeinfo, name, alias) - - # @@@ (Jon) something needs to be done here to handle aliases, probably - - category = typeinfo.elem.get('category') - if category in ('struct', 'union'): - self.genStruct(typeinfo, name, alias) |