1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2021 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 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 what container type you'd like, 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 / (basename + '.txt')) 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('|<<VkCommandBufferLevel,Command Buffer Levels>>|<<vkCmdBeginRenderPass,Render Pass Scope>>|<<VkQueueFlagBits,Supported Queue Types>>', 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 paramIsPointer(self, param): 311 """Check if the parameter passed in is a pointer.""" 312 tail = param.find('type').tail 313 return tail is not None and '*' in tail 314 315 def paramIsStaticArray(self, param): 316 """Check if the parameter passed in is a static array.""" 317 tail = param.find('name').tail 318 return tail and tail[0] == '[' 319 320 def paramIsConst(self, param): 321 """Check if the parameter passed in has a type that mentions const.""" 322 return param.text is not None and 'const' in param.text 323 324 def staticArrayLength(self, param): 325 """Get the length of a parameter that's been identified as a static array.""" 326 paramenumsize = param.find('enum') 327 if paramenumsize is not None: 328 return paramenumsize.text 329 # TODO switch to below when cosmetic changes OK 330 # return self.makeEnumerantName(paramenumsize.text) 331 332 return param.find('name').tail[1:-1] 333 334 def paramIsArray(self, param): 335 """Check if the parameter passed in is a pointer to an array.""" 336 return param.get('len') is not None 337 338 def getHandleDispatchableAncestors(self, typename): 339 """Get the ancestors of a handle object.""" 340 ancestors = [] 341 current = typename 342 while True: 343 current = self.getHandleParent(current) 344 if current is None: 345 return ancestors 346 if self.isHandleTypeDispatchable(current): 347 ancestors.append(current) 348 349 def isHandleTypeDispatchable(self, handlename): 350 """Check if a parent object is dispatchable or not.""" 351 handle = self.registry.tree.find( 352 "types/type/[name='" + handlename + "'][@category='handle']") 353 if handle is not None and getElemType(handle) == 'VK_DEFINE_HANDLE': 354 return True 355 else: 356 return False 357 358 def isHandleOptional(self, param, params): 359 # Simple, if it's optional, return true 360 if param.get('optional') is not None: 361 return True 362 363 # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes. 364 if param.get('noautovalidity') is not None: 365 return True 366 367 # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional 368 if self.paramIsArray(param): 369 for length in LengthEntry.parse_len_from_param(param): 370 if not length.other_param_name: 371 # don't care about constants or "null-terminated" 372 continue 373 374 other_param = findNamedElem(params, length.other_param_name) 375 if other_param is None: 376 self.logMsg('warn', length.other_param_name, 377 'is listed as a length for parameter', param, 'but no such parameter exists') 378 if other_param and other_param.get('optional'): 379 return True 380 381 return False 382 383 def makeOptionalPre(self, param): 384 # Don't generate this stub for bitflags 385 param_name = getElemName(param) 386 paramtype = getElemType(param) 387 type_category = self.getTypeCategory(paramtype) 388 is_optional = param.get('optional').split(',')[0] == 'true' 389 if type_category != 'bitmask' and is_optional: 390 if self.paramIsArray(param) or self.paramIsPointer(param): 391 optional_val = self.null 392 elif type_category == 'handle': 393 if self.isHandleTypeDispatchable(paramtype): 394 optional_val = self.null 395 else: 396 optional_val = 'dlink:' + self.conventions.api_prefix + 'NULL_HANDLE' 397 else: 398 optional_val = self.conventions.zero 399 return 'If {} is not {}, '.format( 400 self.makeParameterName(param_name), 401 optional_val) 402 403 return "" 404 405 def makeParamValidityPre(self, param, params, selector): 406 """Make the start of an entry for a parameter's validity, including a chunk of text if it is an array.""" 407 param_name = getElemName(param) 408 paramtype = getElemType(param) 409 410 # General pre-amble. Check optionality and add stuff. 411 entry = ValidityEntry(anchor=(param_name, 'parameter')) 412 is_optional = param.get('optional') is not None and param.get('optional').split(',')[0] == 'true' 413 414 # This is for a union member, and the valid member is chosen by an enum selection 415 if selector: 416 selection = param.get('selection') 417 418 entry += 'If {} is {}, '.format( 419 self.makeParameterName(selector), 420 self.makeEnumerantName(selection)) 421 422 if is_optional: 423 entry += "and " 424 optionalpre = self.makeOptionalPre(param) 425 entry += optionalpre[0].lower() + optionalpre[1:] 426 427 return entry 428 429 if self.paramIsStaticArray(param): 430 if paramtype != 'char': 431 entry += 'Any given element of ' 432 return entry 433 434 if self.paramIsArray(param) and param.get('len') != LengthEntry.NULL_TERMINATED_STRING: 435 # Find all the parameters that are called out as optional, 436 # so we can document that they might be zero, and the array may be ignored 437 optionallengths = [] 438 for length in LengthEntry.parse_len_from_param(param): 439 if not length.other_param_name: 440 # Only care about length entries that are parameter names 441 continue 442 443 other_param = findNamedElem(params, length.other_param_name) 444 other_param_optional = (other_param is not None) and ( 445 other_param.get('optional') is not None) 446 447 if other_param is None or not other_param_optional: 448 # Don't care about not-found params or non-optional params 449 continue 450 451 if self.paramIsPointer(other_param): 452 optionallengths.append( 453 'the value referenced by ' + self.makeParameterName(length.other_param_name)) 454 else: 455 optionallengths.append( 456 self.makeParameterName(length.other_param_name)) 457 458 # Document that these arrays may be ignored if any of the length values are 0 459 if optionallengths or is_optional: 460 entry += 'If ' 461 if optionallengths: 462 entry += self.makeProseListIs(optionallengths, fmt=plf.OR) 463 entry += ' not %s, ' % self.conventions.zero 464 # TODO enabling this in OpenXR, as used in Vulkan, causes nonsensical things like 465 # "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" 466 if optionallengths and is_optional: 467 entry += 'and ' 468 if is_optional: 469 entry += self.makeParameterName(param_name) 470 # TODO switch when cosmetic changes OK 471 # entry += ' is not {}, '.format(self.null) 472 entry += ' is not `NULL`, ' 473 return entry 474 475 if param.get('optional'): 476 entry += self.makeOptionalPre(param) 477 return entry 478 479 # If none of the early returns happened, we at least return an empty 480 # entry with an anchor. 481 return entry 482 483 def createValidationLineForParameterImpl(self, blockname, param, params, typetext, selector, parentname): 484 """Make the generic validity portion used for all parameters. 485 486 May return None if nothing to validate. 487 """ 488 if param.get('noautovalidity') is not None: 489 return None 490 491 validity = self.makeValidityCollection(blockname) 492 param_name = getElemName(param) 493 paramtype = getElemType(param) 494 495 entry = self.makeParamValidityPre(param, params, selector) 496 497 # This is for a child member of a union 498 if selector: 499 entry += 'the {} member of {} must: be '.format(self.makeParameterName(param_name), self.makeParameterName(parentname)) 500 else: 501 entry += '{} must: be '.format(self.makeParameterName(param_name)) 502 503 if self.paramIsStaticArray(param) and paramtype == 'char': 504 # TODO this is a minor hack to determine if this is a command parameter or a struct member 505 if self.paramIsConst(param) or blockname.startswith(self.conventions.type_prefix): 506 entry += 'a null-terminated UTF-8 string whose length is less than or equal to ' 507 entry += self.staticArrayLength(param) 508 else: 509 # This is a command's output parameter 510 entry += 'a character array of length %s ' % self.staticArrayLength(param) 511 validity += entry 512 return validity 513 514 elif self.paramIsArray(param): 515 # Arrays. These are hard to get right, apparently 516 517 lengths = LengthEntry.parse_len_from_param(param) 518 519 for i, length in enumerate(LengthEntry.parse_len_from_param(param)): 520 if i == 0: 521 # If the first index, make it singular. 522 entry += 'a ' 523 array_text = 'an array' 524 pointer_text = self.valid_pointer_text 525 else: 526 array_text = 'arrays' 527 pointer_text = self.valid_pointer_text + 's' 528 529 if length.null_terminated: 530 # This should always be the last thing. 531 # If it ever isn't for some bizarre reason, then this will need some massaging. 532 entry += 'null-terminated ' 533 elif length.number == 1: 534 entry += pointer_text 535 entry += ' to ' 536 else: 537 entry += pointer_text 538 entry += ' to ' 539 entry += array_text 540 entry += ' of ' 541 # Handle equations, which are currently denoted with latex 542 if length.math: 543 # Handle equations, which are currently denoted with latex 544 entry += str(length) 545 else: 546 entry += self.makeParameterName(str(length)) 547 entry += ' ' 548 549 # Void pointers don't actually point at anything - remove the word "to" 550 if paramtype == 'void': 551 if lengths[-1].number == 1: 552 if len(lengths) > 1: 553 # Take care of the extra s added by the post array chunk function. #HACK# 554 entry.drop_end(5) 555 else: 556 entry.drop_end(4) 557 558 # This hasn't been hit, so this hasn't been tested recently. 559 raise UnhandledCaseError( 560 "Got void pointer param/member with last length 1") 561 else: 562 # An array of void values is a byte array. 563 entry += 'byte' 564 565 elif paramtype == 'char': 566 # A null terminated array of chars is a string 567 if lengths[-1].null_terminated: 568 entry += 'UTF-8 string' 569 else: 570 # Else it's just a bunch of chars 571 entry += 'char value' 572 573 elif self.paramIsConst(param): 574 # If a value is "const" that means it won't get modified, so it must be valid going into the function. 575 if 'const' in param.text: 576 577 if not self.isStructAlwaysValid(paramtype): 578 entry += 'valid ' 579 580 # Check if the array elements are optional 581 array_element_optional = param.get('optional') is not None \ 582 and len(param.get('optional').split(',')) == len(LengthEntry.parse_len_from_param(param)) + 1 \ 583 and param.get('optional').split(',')[-1] == 'true' 584 if array_element_optional and self.getTypeCategory(paramtype) != 'bitmask': # bitmask is handled later 585 entry += 'or dlink:' + self.conventions.api_prefix + 'NULL_HANDLE ' 586 587 entry += typetext 588 589 # pluralize 590 if len(lengths) > 1 or (lengths[0] != 1 and not lengths[0].null_terminated): 591 entry += 's' 592 593 return self.handleRequiredBitmask(blockname, param, paramtype, entry, 'true' if array_element_optional else None) 594 595 if self.paramIsPointer(param): 596 # Handle pointers - which are really special case arrays (i.e. they don't have a length) 597 # TODO should do something here if someone ever uses some intricate comma-separated `optional` 598 pointercount = param.find('type').tail.count('*') 599 600 # Treat void* as an int 601 if paramtype == 'void': 602 optional = param.get('optional') 603 # If there is only void*, it is just optional int - we don't need any language. 604 if pointercount == 1 and optional is not None: 605 return None # early return 606 # Treat the inner-most void* as an int 607 pointercount -= 1 608 609 # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that. 610 entry += 'a ' 611 entry += (self.valid_pointer_text + ' to a ') * pointercount 612 613 # Handle void* and pointers to it 614 if paramtype == 'void': 615 if optional is None or optional.split(',')[pointercount]: 616 # The last void* is just optional int (e.g. to be filled by the impl.) 617 typetext = 'pointer value' 618 619 # If a value is "const" that means it won't get modified, so it must be valid going into the function. 620 elif self.paramIsConst(param) and paramtype != 'void': 621 entry += 'valid ' 622 623 entry += typetext 624 return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional')) 625 626 # Add additional line for non-optional bitmasks 627 if self.getTypeCategory(paramtype) == 'bitmask': 628 # TODO does not really handle if someone tries something like optional="true,false" 629 # TODO OpenXR has 0 or a valid combination of flags, for optional things. 630 # Vulkan doesn't... 631 # isMandatory = param.get('optional') is None 632 # if not isMandatory: 633 # entry += self.conventions.zero 634 # entry += ' or ' 635 # Non-pointer, non-optional things must be valid 636 entry += 'a valid {}'.format(typetext) 637 638 return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional')) 639 640 # Non-pointer, non-optional things must be valid 641 entry += 'a valid {}'.format(typetext) 642 return entry 643 644 def handleRequiredBitmask(self, blockname, param, paramtype, entry, optional): 645 # TODO does not really handle if someone tries something like optional="true,false" 646 if self.getTypeCategory(paramtype) != 'bitmask' or optional == 'true': 647 return entry 648 if self.paramIsPointer(param) and not self.paramIsArray(param): 649 # This is presumably an output parameter 650 return entry 651 652 param_name = getElemName(param) 653 # If mandatory, then we need two entries instead of just one. 654 validity = self.makeValidityCollection(blockname) 655 validity += entry 656 657 entry2 = ValidityEntry(anchor=(param_name, 'requiredbitmask')) 658 if self.paramIsArray(param): 659 entry2 += 'Each element of ' 660 entry2 += '{} must: not be {}'.format( 661 self.makeParameterName(param_name), self.conventions.zero) 662 validity += entry2 663 return validity 664 665 def createValidationLineForParameter(self, blockname, param, params, typecategory, selector, parentname): 666 """Make an entire validation entry for a given parameter.""" 667 param_name = getElemName(param) 668 paramtype = getElemType(param) 669 670 is_array = self.paramIsArray(param) 671 is_pointer = self.paramIsPointer(param) 672 needs_recursive_validity = (is_array 673 or is_pointer 674 or not self.isStructAlwaysValid(paramtype)) 675 typetext = None 676 if paramtype in ('void', 'char'): 677 # Chars and void are special cases - we call the impl function, 678 # but don't use the typetext. 679 # A null-terminated char array is a string, else it's chars. 680 # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular 681 typetext = '' 682 683 elif typecategory == 'bitmask': 684 bitsname = paramtype.replace('Flags', 'FlagBits') 685 bitselem = self.registry.tree.find("enums[@name='" + bitsname + "']") 686 687 # If bitsname is an alias, then use the alias to get bitselem. 688 typeElem = self.registry.lookupElementInfo(bitsname, self.registry.typedict) 689 if typeElem is not None: 690 alias = self.registry.getAlias(typeElem.elem, self.registry.typedict) 691 if alias is not None: 692 bitselem = self.registry.tree.find("enums[@name='" + alias + "']") 693 694 if bitselem is None or len(bitselem.findall('enum[@required="true"]')) == 0: 695 # Empty bit mask: presumably just a placeholder (or only in 696 # an extension not enabled for this build) 697 entry = ValidityEntry( 698 anchor=(param_name, 'zerobitmask')) 699 entry += self.makeParameterName(param_name) 700 entry += ' must: be ' 701 entry += self.conventions.zero 702 # Early return 703 return entry 704 705 is_const = self.paramIsConst(param) 706 707 if is_array: 708 if is_const: 709 # input an array of bitmask values 710 template = 'combinations of {bitsname} value' 711 else: 712 template = '{paramtype} value' 713 elif is_pointer: 714 if is_const: 715 template = 'combination of {bitsname} values' 716 else: 717 template = '{paramtype} value' 718 else: 719 template = 'combination of {bitsname} values' 720 721 # The above few cases all use makeEnumerationName, just with different context. 722 typetext = template.format( 723 bitsname=self.makeEnumerationName(bitsname), 724 paramtype=self.makeFlagsName(paramtype)) 725 726 elif typecategory == 'handle': 727 typetext = '{} handle'.format(self.makeStructName(paramtype)) 728 729 elif typecategory == 'enum': 730 typetext = '{} value'.format(self.makeEnumerationName(paramtype)) 731 732 elif typecategory == 'funcpointer': 733 typetext = '{} value'.format(self.makeFuncPointerName(paramtype)) 734 735 elif typecategory == 'struct': 736 if needs_recursive_validity: 737 typetext = '{} structure'.format( 738 self.makeStructName(paramtype)) 739 740 elif typecategory == 'union': 741 if needs_recursive_validity: 742 typetext = '{} union'.format(self.makeStructName(paramtype)) 743 744 elif self.paramIsArray(param) or self.paramIsPointer(param): 745 # TODO sync cosmetic changes from OpenXR? 746 typetext = '{} value'.format(self.makeBaseTypeName(paramtype)) 747 748 elif typecategory is None: 749 if not self.isStructAlwaysValid(paramtype): 750 typetext = '{} value'.format( 751 self.makeExternalTypeName(paramtype)) 752 753 # "a valid uint32_t value" doesn't make much sense. 754 pass 755 756 # If any of the above conditions matched and set typetext, 757 # we call using it. 758 if typetext is not None: 759 return self.createValidationLineForParameterImpl( 760 blockname, param, params, typetext, selector, parentname) 761 return None 762 763 def makeHandleValidityParent(self, param, params): 764 """Make a validity entry for a handle's parent object. 765 766 Creates 'parent' VUID. 767 """ 768 param_name = getElemName(param) 769 paramtype = getElemType(param) 770 771 # Deal with handle parents 772 handleparent = self.getHandleParent(paramtype) 773 if handleparent is None: 774 return None 775 776 otherparam = findTypedElem(params, handleparent) 777 if otherparam is None: 778 return None 779 780 parent_name = getElemName(otherparam) 781 entry = ValidityEntry(anchor=(param_name, 'parent')) 782 783 is_optional = self.isHandleOptional(param, params) 784 785 if self.paramIsArray(param): 786 template = 'Each element of {}' 787 if is_optional: 788 template += ' that is a valid handle' 789 elif is_optional: 790 template = 'If {} is a valid handle, it' 791 else: 792 # not optional, not an array. Just say the parameter name. 793 template = '{}' 794 795 entry += template.format(self.makeParameterName(param_name)) 796 797 entry += ' must: have been created, allocated, or retrieved from {}'.format( 798 self.makeParameterName(parent_name)) 799 800 return entry 801 802 def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params): 803 """Make an asciidoc validity entry for a common ancestors between handles. 804 805 Only handles parent validity for signatures taking multiple handles 806 any ancestors also being supplied to this function. 807 (e.g. "Each of x, y, and z must: come from the same slink:ParentHandle") 808 See self.makeAsciiDocHandleParent() for instances where the parent 809 handle is named and also passed. 810 811 Creates 'commonparent' VUID. 812 """ 813 # TODO Replace with refactored code from OpenXR 814 entry = None 815 816 if len(handles) > 1: 817 ancestormap = {} 818 anyoptional = False 819 # Find all the ancestors 820 for param in handles: 821 paramtype = getElemType(param) 822 823 if not self.paramIsPointer(param) or (param.text and 'const' in param.text): 824 ancestors = self.getHandleDispatchableAncestors(paramtype) 825 826 ancestormap[param] = ancestors 827 828 anyoptional |= self.isHandleOptional(param, params) 829 830 # Remove redundant ancestor lists 831 for param in handles: 832 paramtype = getElemType(param) 833 834 removals = [] 835 for ancestors in ancestormap.items(): 836 if paramtype in ancestors[1]: 837 removals.append(ancestors[0]) 838 839 if removals != []: 840 for removal in removals: 841 del(ancestormap[removal]) 842 843 # Intersect 844 845 if len(ancestormap.values()) > 1: 846 current = list(ancestormap.values())[0] 847 for ancestors in list(ancestormap.values())[1:]: 848 current = [val for val in current if val in ancestors] 849 850 if len(current) > 0: 851 commonancestor = current[0] 852 853 if len(ancestormap.keys()) > 1: 854 855 entry = ValidityEntry(anchor=('commonparent',)) 856 857 parametertexts = [] 858 for param in ancestormap.keys(): 859 param_name = getElemName(param) 860 parametertext = self.makeParameterName(param_name) 861 if self.paramIsArray(param): 862 parametertext = 'the elements of ' + parametertext 863 parametertexts.append(parametertext) 864 865 parametertexts.sort() 866 867 if len(parametertexts) > 2: 868 entry += 'Each of ' 869 else: 870 entry += 'Both of ' 871 872 entry += self.makeProseList(parametertexts, 873 comma_for_two_elts=True) 874 if anyoptional is True: 875 entry += ' that are valid handles of non-ignored parameters' 876 entry += ' must: have been created, allocated, or retrieved from the same ' 877 entry += self.makeStructName(commonancestor) 878 879 return entry 880 881 def makeStructureTypeFromName(self, structname): 882 """Create text for a structure type name, like ename:VK_STRUCTURE_TYPE_CREATE_INSTANCE_INFO""" 883 return self.makeEnumerantName(self.conventions.generate_structure_type_from_name(structname)) 884 885 def makeStructureTypeValidity(self, structname): 886 """Generate an validity line for the type value of a struct. 887 888 Creates VUID named like the member name. 889 """ 890 info = self.registry.typedict.get(structname) 891 assert(info is not None) 892 893 # If this fails (meaning we have something other than a struct in here), 894 # then the caller is wrong: 895 # probably passing the wrong value for structname. 896 members = info.getMembers() 897 assert(members) 898 899 # If this fails, see caller: this should only get called for a struct type with a type value. 900 param = findNamedElem(members, self.structtype_member_name) 901 # OpenXR gets some structs without a type field in here, so can't assert 902 assert(param is not None) 903 # if param is None: 904 # return None 905 906 entry = ValidityEntry( 907 anchor=(self.structtype_member_name, self.structtype_member_name)) 908 entry += self.makeParameterName(self.structtype_member_name) 909 entry += ' must: be ' 910 911 values = param.get('values', '').split(',') 912 if values: 913 # Extract each enumerant value. They could be validated in the 914 # same fashion as validextensionstructs in 915 # makeStructureExtensionPointer, although that's not relevant in 916 # the current extension struct model. 917 entry += self.makeProseList((self.makeEnumerantName(v) 918 for v in values), 'or') 919 return entry 920 921 if 'Base' in structname: 922 # This type doesn't even have any values for its type, 923 # and it seems like it might be a base struct that we'd expect to lack its own type, 924 # so omit the entire statement 925 return None 926 927 self.logMsg('warn', 'No values were marked-up for the structure type member of', 928 structname, 'so making one up!') 929 entry += self.makeStructureTypeFromName(structname) 930 931 return entry 932 933 def makeStructureExtensionPointer(self, blockname, param): 934 """Generate an validity line for the pointer chain member value of a struct.""" 935 param_name = getElemName(param) 936 937 if param.get('validextensionstructs') is not None: 938 self.logMsg('warn', blockname, 939 'validextensionstructs is deprecated/removed', '\n') 940 941 entry = ValidityEntry( 942 anchor=(param_name, self.nextpointer_member_name)) 943 validextensionstructs = self.registry.validextensionstructs.get( 944 blockname) 945 extensionstructs = [] 946 duplicatestructs = [] 947 948 if validextensionstructs is not None: 949 # Check each structure name and skip it if not required by the 950 # generator. This allows tagging extension structs in the XML 951 # that are only included in validity when needed for the spec 952 # being targeted. 953 # Track the required structures, and of the required structures, 954 # those that allow duplicates in the pNext chain. 955 for struct in validextensionstructs: 956 # Unpleasantly breaks encapsulation. Should be a method in the registry class 957 t = self.registry.lookupElementInfo( 958 struct, self.registry.typedict) 959 if t is None: 960 self.logMsg('warn', 'makeStructureExtensionPointer: struct', struct, 961 'is in a validextensionstructs= attribute but is not in the registry') 962 elif t.required: 963 extensionstructs.append('slink:' + struct) 964 if t.elem.get('allowduplicate') == 'true': 965 duplicatestructs.append('slink:' + struct) 966 else: 967 self.logMsg( 968 'diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required') 969 970 if not extensionstructs: 971 entry += '{} must: be {}'.format( 972 self.makeParameterName(param_name), self.null) 973 return entry 974 975 if len(extensionstructs) == 1: 976 entry += '{} must: be {} or a pointer to a valid instance of {}'.format(self.makeParameterName(param_name), self.null, 977 extensionstructs[0]) 978 else: 979 # More than one extension struct. 980 entry += 'Each {} member of any structure (including this one) in the pname:{} chain '.format( 981 self.makeParameterName(param_name), self.nextpointer_member_name) 982 entry += 'must: be either {} or a pointer to a valid instance of '.format( 983 self.null) 984 985 entry += self.makeProseList(extensionstructs, fmt=plf.OR) 986 987 validity = self.makeValidityCollection(blockname) 988 validity += entry 989 990 # Generate VU statement requiring unique structures in the pNext 991 # chain. 992 # NOTE: OpenXR always allows non-unique type values. Instances other 993 # than the first are just ignored 994 995 vu = ('The pname:' + 996 self.structtype_member_name + 997 ' value of each struct in the pname:' + 998 self.nextpointer_member_name + 999 ' chain must: be unique') 1000 anchor = (self.conventions.member_used_for_unique_vuid, 'unique') 1001 1002 # If duplicates of some structures are allowed, they are called out 1003 # explicitly. 1004 num = len(duplicatestructs) 1005 if num > 0: 1006 vu = (vu + 1007 ', with the exception of structures of type ' + 1008 self.makeProseList(duplicatestructs, fmt=plf.OR)) 1009 1010 validity.addValidityEntry(vu, anchor = anchor ) 1011 1012 return validity 1013 1014 def addSharedStructMemberValidity(self, struct, blockname, param, validity): 1015 """Generate language to independently validate a parameter, for those validated even in output. 1016 1017 Return value indicates whether it was handled internally (True) or if it may need more validity (False).""" 1018 param_name = getElemName(param) 1019 paramtype = getElemType(param) 1020 if param.get('noautovalidity') is None: 1021 1022 if self.conventions.is_structure_type_member(paramtype, param_name): 1023 validity += self.makeStructureTypeValidity(blockname) 1024 return True 1025 1026 if self.conventions.is_nextpointer_member(paramtype, param_name): 1027 # Vulkan: the addition of validity here is conditional unlike OpenXR. 1028 if struct.get('structextends') is None: 1029 validity += self.makeStructureExtensionPointer( 1030 blockname, param) 1031 return True 1032 return False 1033 1034 def makeOutputOnlyStructValidity(self, cmd, blockname, params): 1035 """Generate all the valid usage information for a struct that's entirely output. 1036 1037 That is, it is only ever filled out by the implementation other than 1038 the structure type and pointer chain members. 1039 Thus, we only create validity for the pointer chain member. 1040 """ 1041 # Start the validity collection for this struct 1042 validity = self.makeValidityCollection(blockname) 1043 1044 for param in params: 1045 self.addSharedStructMemberValidity( 1046 cmd, blockname, param, validity) 1047 1048 return validity 1049 1050 def isVKVersion11(self): 1051 """Returns true if VK_VERSION_1_1 is being emitted.""" 1052 vk11 = re.match(self.registry.genOpts.emitversions, 'VK_VERSION_1_1') is not None 1053 return vk11 1054 1055 def makeStructOrCommandValidity(self, cmd, blockname, params): 1056 """Generate all the valid usage information for a given struct or command.""" 1057 validity = self.makeValidityCollection(blockname) 1058 handles = [] 1059 arraylengths = dict() 1060 for param in params: 1061 param_name = getElemName(param) 1062 paramtype = getElemType(param) 1063 1064 # Valid usage ID tags (VUID) are generated for various 1065 # conditions based on the name of the block (structure or 1066 # command), name of the element (member or parameter), and type 1067 # of VU statement. 1068 1069 # Get the type's category 1070 typecategory = self.getTypeCategory(paramtype) 1071 1072 if not self.addSharedStructMemberValidity( 1073 cmd, blockname, param, validity): 1074 if not param.get('selector'): 1075 validity += self.createValidationLineForParameter( 1076 blockname, param, params, typecategory, None, None) 1077 else: 1078 selector = param.get('selector') 1079 if typecategory != 'union': 1080 self.logMsg('warn', 'selector attribute set on non-union parameter', param_name, 'in', blockname) 1081 1082 paraminfo = self.registry.lookupElementInfo(paramtype, self.registry.typedict) 1083 1084 for member in paraminfo.getMembers(): 1085 membertype = getElemType(member) 1086 membertypecategory = self.getTypeCategory(membertype) 1087 1088 validity += self.createValidationLineForParameter( 1089 blockname, member, paraminfo.getMembers(), membertypecategory, selector, param_name) 1090 1091 # Ensure that any parenting is properly validated, and list that a handle was found 1092 if typecategory == 'handle': 1093 handles.append(param) 1094 1095 # Get the array length for this parameter 1096 lengths = LengthEntry.parse_len_from_param(param) 1097 if lengths: 1098 arraylengths.update({length.other_param_name: length 1099 for length in lengths 1100 if length.other_param_name}) 1101 1102 # For any vkQueue* functions, there might be queue type data 1103 if 'vkQueue' in blockname: 1104 # The queue type must be valid 1105 queuebits = _parse_queue_bits(cmd) 1106 if queuebits: 1107 entry = ValidityEntry(anchor=('queuetype',)) 1108 entry += 'The pname:queue must: support ' 1109 entry += self.makeProseList(queuebits, 1110 fmt=plf.OR, comma_for_two_elts=True) 1111 entry += ' operations' 1112 validity += entry 1113 1114 if 'vkCmd' in blockname: 1115 # The commandBuffer parameter must be being recorded 1116 entry = ValidityEntry(anchor=('commandBuffer', 'recording')) 1117 entry += 'pname:commandBuffer must: be in the <<commandbuffers-lifecycle, recording state>>' 1118 validity += entry 1119 1120 # 1121 # Start of valid queue type validation - command pool must have been 1122 # allocated against a queue with at least one of the valid queue types 1123 entry = ValidityEntry(anchor=('commandBuffer', 'cmdpool')) 1124 1125 # 1126 # This test for vkCmdFillBuffer is a hack, since we have no path 1127 # to conditionally have queues enabled or disabled by an extension. 1128 # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now 1129 if blockname == 'vkCmdFillBuffer': 1130 entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support ' 1131 if self.isVKVersion11() or 'VK_KHR_maintenance1' in self.registry.requiredextensions: 1132 entry += 'transfer, graphics or compute operations' 1133 else: 1134 entry += 'graphics or compute operations' 1135 else: 1136 # The queue type must be valid 1137 queuebits = _parse_queue_bits(cmd) 1138 assert(queuebits) 1139 entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support ' 1140 entry += self.makeProseList(queuebits, 1141 fmt=plf.OR, comma_for_two_elts=True) 1142 entry += ' operations' 1143 validity += entry 1144 1145 # Must be called inside/outside a render pass appropriately 1146 renderpass = cmd.get('renderpass') 1147 1148 if renderpass != 'both': 1149 entry = ValidityEntry(anchor=('renderpass',)) 1150 entry += 'This command must: only be called ' 1151 entry += renderpass 1152 entry += ' of a render pass instance' 1153 validity += entry 1154 1155 # Must be in the right level command buffer 1156 cmdbufferlevel = cmd.get('cmdbufferlevel') 1157 1158 if cmdbufferlevel != 'primary,secondary': 1159 entry = ValidityEntry(anchor=('bufferlevel',)) 1160 entry += 'pname:commandBuffer must: be a ' 1161 entry += cmdbufferlevel 1162 entry += ' sname:VkCommandBuffer' 1163 validity += entry 1164 1165 # Any non-optional arraylengths should specify they must be greater than 0 1166 array_length_params = ((param, getElemName(param)) 1167 for param in params 1168 if getElemName(param) in arraylengths) 1169 1170 for param, param_name in array_length_params: 1171 if param.get('optional') is not None: 1172 continue 1173 1174 length = arraylengths[param_name] 1175 full_length = length.full_reference 1176 1177 # Is this just a name of a param? If false, then it's some kind of qualified name (a member of a param for instance) 1178 simple_param_reference = (len(length.param_ref_parts) == 1) 1179 if not simple_param_reference: 1180 # Loop through to see if any parameters in the chain are optional 1181 array_length_parent = cmd 1182 array_length_optional = False 1183 for part in length.param_ref_parts: 1184 # Overwrite the param so it ends up as the bottom level parameter for later checks 1185 param = array_length_parent.find("*/[name='{}']".format(part)) 1186 1187 # If any parameter in the chain is optional, skip the implicit length requirement 1188 array_length_optional |= (param.get('optional') is not None) 1189 1190 # Lookup the type of the parameter for the next loop iteration 1191 type = param.findtext('type') 1192 array_length_parent = self.registry.tree.find("./types/type/[@name='{}']".format(type)) 1193 1194 if array_length_optional: 1195 continue 1196 1197 # Get all the array dependencies 1198 arrays = cmd.findall( 1199 "param/[@len='{}'][@optional='true']".format(full_length)) 1200 1201 # Get all the optional array dependencies, including those not generating validity for some reason 1202 optionalarrays = arrays + \ 1203 cmd.findall( 1204 "param/[@len='{}'][@noautovalidity='true']".format(full_length)) 1205 1206 entry = ValidityEntry(anchor=(full_length, 'arraylength')) 1207 # Allow lengths to be arbitrary if all their dependents are optional 1208 if optionalarrays and len(optionalarrays) == len(arrays): 1209 entry += 'If ' 1210 # TODO sync this section from OpenXR once cosmetic changes OK 1211 1212 optional_array_names = (self.makeParameterName(getElemName(array)) 1213 for array in optionalarrays) 1214 entry += self.makeProseListIs(optional_array_names, 1215 plf.ANY_OR, comma_for_two_elts=True) 1216 1217 entry += ' not {}, '.format(self.null) 1218 1219 # TODO end needs sync cosmetic 1220 if self.paramIsPointer(param): 1221 entry += 'the value referenced by ' 1222 1223 # Split and re-join here to insert pname: around :: 1224 entry += '::'.join(self.makeParameterName(part) 1225 for part in full_length.split('::')) 1226 # TODO replace the previous statement with the following when cosmetic changes OK 1227 # entry += length.get_human_readable(make_param_name=self.makeParameterName) 1228 1229 entry += ' must: be greater than ' 1230 entry += self.conventions.zero 1231 validity += entry 1232 1233 # Find the parents of all objects referenced in this command 1234 for param in handles: 1235 # Don't detect a parent for return values! 1236 if not self.paramIsPointer(param) or self.paramIsConst(param): 1237 validity += self.makeHandleValidityParent(param, params) 1238 1239 # Find the common ancestor of all objects referenced in this command 1240 validity += self.makeAsciiDocHandlesCommonAncestor( 1241 blockname, handles, params) 1242 1243 return validity 1244 1245 def makeThreadSafetyBlock(self, cmd, paramtext): 1246 """Generate thread-safety validity entries for cmd/structure""" 1247 # See also makeThreadSafetyBlock in validitygenerator.py 1248 validity = self.makeValidityCollection(getElemName(cmd)) 1249 1250 # This text varies between projects, so an Asciidoctor attribute is used. 1251 extsync_prefix = "{externsyncprefix} " 1252 1253 # Find and add any parameters that are thread unsafe 1254 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]") 1255 if explicitexternsyncparams is not None: 1256 for param in explicitexternsyncparams: 1257 externsyncattribs = ExternSyncEntry.parse_externsync_from_param( 1258 param) 1259 param_name = getElemName(param) 1260 1261 for attrib in externsyncattribs: 1262 entry = ValidityEntry() 1263 entry += extsync_prefix 1264 if attrib.entirely_extern_sync: 1265 if self.paramIsArray(param): 1266 entry += 'each member of ' 1267 elif self.paramIsPointer(param): 1268 entry += 'the object referenced by ' 1269 1270 entry += self.makeParameterName(param_name) 1271 1272 if attrib.children_extern_sync: 1273 entry += ', and any child handles,' 1274 1275 else: 1276 entry += 'pname:' 1277 entry += str(attrib.full_reference) 1278 # TODO switch to the following when cosmetic changes OK 1279 # entry += attrib.get_human_readable(make_param_name=self.makeParameterName) 1280 entry += ' must: be externally synchronized' 1281 validity += entry 1282 1283 # Vulkan-specific 1284 # For any vkCmd* functions, the command pool is externally synchronized 1285 if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name').text: 1286 entry = ValidityEntry() 1287 entry += extsync_prefix 1288 entry += 'the sname:VkCommandPool that pname:commandBuffer was allocated from must: be externally synchronized' 1289 validity += entry 1290 1291 # Find and add any "implicit" parameters that are thread unsafe 1292 implicitexternsyncparams = cmd.find('implicitexternsyncparams') 1293 if implicitexternsyncparams is not None: 1294 for elem in implicitexternsyncparams: 1295 entry = ValidityEntry() 1296 entry += extsync_prefix 1297 entry += elem.text 1298 entry += ' must: be externally synchronized' 1299 validity += entry 1300 1301 return validity 1302 1303 def makeCommandPropertiesTableEntry(self, cmd, name): 1304 1305 if 'vkCmd' in name: 1306 # Must be called inside/outside a render pass appropriately 1307 cmdbufferlevel = cmd.get('cmdbufferlevel') 1308 cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(',')) 1309 1310 renderpass = cmd.get('renderpass') 1311 renderpass = renderpass.capitalize() 1312 1313 # 1314 # This test for vkCmdFillBuffer is a hack, since we have no path 1315 # to conditionally have queues enabled or disabled by an extension. 1316 # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now 1317 if name == 'vkCmdFillBuffer': 1318 if self.isVKVersion11() or 'VK_KHR_maintenance1' in self.registry.requiredextensions: 1319 queues = 'Transfer + \nGraphics + \nCompute' 1320 else: 1321 queues = 'Graphics + \nCompute' 1322 else: 1323 queues = cmd.get('queues') 1324 queues = (' + \n').join(queues.title().split(',')) 1325 1326 return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues 1327 elif 'vkQueue' in name: 1328 # Must be called inside/outside a render pass appropriately 1329 1330 queues = cmd.get('queues') 1331 if queues is None: 1332 queues = 'Any' 1333 else: 1334 queues = (' + \n').join(queues.upper().split(',')) 1335 1336 return '|-|-|' + queues 1337 1338 return None 1339 1340 1341 def findRequiredEnums(self, enums): 1342 """Check each enumerant name in the enums list and remove it if not 1343 required by the generator. This allows specifying success and error 1344 codes for extensions that are only included in validity when needed 1345 for the spec being targeted.""" 1346 return self.keepOnlyRequired(enums, self.registry.enumdict) 1347 1348 def findRequiredCommands(self, commands): 1349 """Check each command name in the commands list and remove it if not 1350 required by the generator. 1351 1352 This will allow some state operations to take place before endFile.""" 1353 return self.keepOnlyRequired(commands, self.registry.cmddict) 1354 1355 def keepOnlyRequired(self, names, info_dict): 1356 """Check each element name in the supplied dictionary and remove it if not 1357 required by the generator. 1358 1359 This will allow some operations to take place before endFile no matter the order of generation.""" 1360 # TODO Unpleasantly breaks encapsulation. Should be a method in the registry class 1361 1362 def is_required(name): 1363 info = self.registry.lookupElementInfo(name, info_dict) 1364 if info is None: 1365 return False 1366 if not info.required: 1367 self.logMsg('diag', 'keepOnlyRequired: element', 1368 name, 'IS NOT required, skipping') 1369 return info.required 1370 1371 return [name 1372 for name in names 1373 if is_required(name)] 1374 1375 def makeReturnCodeList(self, attrib, cmd, name): 1376 """Return a list of possible return codes for a function. 1377 1378 attrib is either 'successcodes' or 'errorcodes'. 1379 """ 1380 return_lines = [] 1381 RETURN_CODE_FORMAT = '* ename:{}' 1382 1383 codes_attr = cmd.get(attrib) 1384 if codes_attr: 1385 codes = self.findRequiredEnums(codes_attr.split(',')) 1386 if codes: 1387 return_lines.extend((RETURN_CODE_FORMAT.format(code) 1388 for code in codes)) 1389 1390 applicable_ext_codes = (ext_code 1391 for ext_code in self.registry.commandextensionsuccesses 1392 if ext_code.command == name) 1393 for ext_code in applicable_ext_codes: 1394 line = RETURN_CODE_FORMAT.format(ext_code.value) 1395 if ext_code.extension: 1396 line += ' [only if {} is enabled]'.format( 1397 self.conventions.formatExtension(ext_code.extension)) 1398 1399 return_lines.append(line) 1400 if return_lines: 1401 return '\n'.join(return_lines) 1402 1403 return None 1404 1405 def makeSuccessCodes(self, cmd, name): 1406 return self.makeReturnCodeList('successcodes', cmd, name) 1407 1408 def makeErrorCodes(self, cmd, name): 1409 return self.makeReturnCodeList('errorcodes', cmd, name) 1410 1411 def genCmd(self, cmdinfo, name, alias): 1412 """Command generation.""" 1413 OutputGenerator.genCmd(self, cmdinfo, name, alias) 1414 1415 # @@@ (Jon) something needs to be done here to handle aliases, probably 1416 1417 validity = self.makeValidityCollection(name) 1418 1419 # OpenXR-only: make sure extension is enabled 1420 # validity.possiblyAddExtensionRequirement(self.currentExtension, 'calling flink:') 1421 1422 validity += self.makeStructOrCommandValidity( 1423 cmdinfo.elem, name, cmdinfo.getParams()) 1424 1425 threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param') 1426 commandpropertiesentry = None 1427 1428 # Vulkan-specific 1429 commandpropertiesentry = self.makeCommandPropertiesTableEntry( 1430 cmdinfo.elem, name) 1431 successcodes = self.makeSuccessCodes(cmdinfo.elem, name) 1432 errorcodes = self.makeErrorCodes(cmdinfo.elem, name) 1433 1434 # OpenXR-specific 1435 # self.generateStateValidity(validity, name) 1436 1437 self.writeInclude('protos', name, validity, threadsafety, 1438 commandpropertiesentry, successcodes, errorcodes) 1439 1440 def genStruct(self, typeinfo, typeName, alias): 1441 """Struct Generation.""" 1442 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 1443 1444 # @@@ (Jon) something needs to be done here to handle aliases, probably 1445 1446 # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information. 1447 validity = self.makeValidityCollection(typeName) 1448 threadsafety = [] 1449 1450 # OpenXR-only: make sure extension is enabled 1451 # validity.possiblyAddExtensionRequirement(self.currentExtension, 'using slink:') 1452 1453 if typeinfo.elem.get('category') != 'union': 1454 if typeinfo.elem.get('returnedonly') is None: 1455 validity += self.makeStructOrCommandValidity( 1456 typeinfo.elem, typeName, typeinfo.getMembers()) 1457 threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member') 1458 1459 else: 1460 # Need to generate structure type and next pointer chain member validation 1461 validity += self.makeOutputOnlyStructValidity( 1462 typeinfo.elem, typeName, typeinfo.getMembers()) 1463 1464 self.writeInclude('structs', typeName, validity, 1465 threadsafety, None, None, None) 1466 1467 def genGroup(self, groupinfo, groupName, alias): 1468 """Group (e.g. C "enum" type) generation. 1469 For the validity generator, this just tags individual enumerants 1470 as required or not. 1471 """ 1472 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 1473 1474 # @@@ (Jon) something needs to be done here to handle aliases, probably 1475 1476 groupElem = groupinfo.elem 1477 1478 # Loop over the nested 'enum' tags. Keep track of the minimum and 1479 # maximum numeric values, if they can be determined; but only for 1480 # core API enumerants, not extension enumerants. This is inferred 1481 # by looking for 'extends' attributes. 1482 for elem in groupElem.findall('enum'): 1483 name = elem.get('name') 1484 ei = self.registry.lookupElementInfo(name, self.registry.enumdict) 1485 1486 # Tag enumerant as required or not 1487 ei.required = self.isEnumRequired(elem) 1488 1489 def genType(self, typeinfo, name, alias): 1490 """Type Generation.""" 1491 OutputGenerator.genType(self, typeinfo, name, alias) 1492 1493 # @@@ (Jon) something needs to be done here to handle aliases, probably 1494 1495 category = typeinfo.elem.get('category') 1496 if category in ('struct', 'union'): 1497 self.genStruct(typeinfo, name, alias) 1498