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