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