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