blob: cdca69ab9642a34054ae7b55979792c01c7fd15d [file] [log] [blame]
Mike Stroyana451fa82016-01-07 15:35:37 -07001#!/usr/bin/python3 -i
2import os,re,sys
Dustin Gravesf69e3772016-02-11 10:10:14 -07003from collections import namedtuple
4from lxml import etree
Mike Stroyana451fa82016-01-07 15:35:37 -07005
6def write( *args, **kwargs ):
7 file = kwargs.pop('file',sys.stdout)
8 end = kwargs.pop( 'end','\n')
9 file.write( ' '.join([str(arg) for arg in args]) )
10 file.write( end )
11
12# noneStr - returns string argument, or "" if argument is None.
13# Used in converting lxml Elements into text.
14# str - string to convert
15def noneStr(str):
16 if (str):
17 return str
18 else:
19 return ""
20
21# enquote - returns string argument with surrounding quotes,
22# for serialization into Python code.
23def enquote(str):
24 if (str):
25 return "'" + str + "'"
26 else:
27 return None
28
29# Primary sort key for regSortFeatures.
30# Sorts by category of the feature name string:
31# Core API features (those defined with a <feature> tag)
32# ARB/KHR/OES (Khronos extensions)
33# other (EXT/vendor extensions)
34# This will need changing for Vulkan!
35def regSortCategoryKey(feature):
36 if (feature.elem.tag == 'feature'):
37 return 0
38 elif (feature.category == 'ARB' or
39 feature.category == 'KHR' or
40 feature.category == 'OES'):
41 return 1
42 else:
43 return 2
44
45# Secondary sort key for regSortFeatures.
46# Sorts by extension name.
47def regSortNameKey(feature):
48 return feature.name
49
50# Second sort key for regSortFeatures.
51# Sorts by feature version. <extension> elements all have version number "0"
52def regSortFeatureVersionKey(feature):
53 return float(feature.version)
54
55# Tertiary sort key for regSortFeatures.
56# Sorts by extension number. <feature> elements all have extension number 0.
57def regSortExtensionNumberKey(feature):
58 return int(feature.number)
59
60# regSortFeatures - default sort procedure for features.
61# Sorts by primary key of feature category ('feature' or 'extension')
62# then by version number (for features)
63# then by extension number (for extensions)
64def regSortFeatures(featureList):
65 featureList.sort(key = regSortExtensionNumberKey)
66 featureList.sort(key = regSortFeatureVersionKey)
67 featureList.sort(key = regSortCategoryKey)
68
69# GeneratorOptions - base class for options used during header production
70# These options are target language independent, and used by
71# Registry.apiGen() and by base OutputGenerator objects.
72#
73# Members
74# filename - name of file to generate, or None to write to stdout.
75# apiname - string matching <api> 'apiname' attribute, e.g. 'gl'.
76# profile - string specifying API profile , e.g. 'core', or None.
77# versions - regex matching API versions to process interfaces for.
78# Normally '.*' or '[0-9]\.[0-9]' to match all defined versions.
79# emitversions - regex matching API versions to actually emit
80# interfaces for (though all requested versions are considered
81# when deciding which interfaces to generate). For GL 4.3 glext.h,
82# this might be '1\.[2-5]|[2-4]\.[0-9]'.
83# defaultExtensions - If not None, a string which must in its
84# entirety match the pattern in the "supported" attribute of
85# the <extension>. Defaults to None. Usually the same as apiname.
86# addExtensions - regex matching names of additional extensions
87# to include. Defaults to None.
88# removeExtensions - regex matching names of extensions to
89# remove (after defaultExtensions and addExtensions). Defaults
90# to None.
91# sortProcedure - takes a list of FeatureInfo objects and sorts
92# them in place to a preferred order in the generated output.
93# Default is core API versions, ARB/KHR/OES extensions, all
94# other extensions, alphabetically within each group.
95# The regex patterns can be None or empty, in which case they match
96# nothing.
97class GeneratorOptions:
98 """Represents options during header production from an API registry"""
99 def __init__(self,
100 filename = None,
101 apiname = None,
102 profile = None,
103 versions = '.*',
104 emitversions = '.*',
105 defaultExtensions = None,
106 addExtensions = None,
107 removeExtensions = None,
108 sortProcedure = regSortFeatures):
109 self.filename = filename
110 self.apiname = apiname
111 self.profile = profile
112 self.versions = self.emptyRegex(versions)
113 self.emitversions = self.emptyRegex(emitversions)
114 self.defaultExtensions = defaultExtensions
115 self.addExtensions = self.emptyRegex(addExtensions)
116 self.removeExtensions = self.emptyRegex(removeExtensions)
117 self.sortProcedure = sortProcedure
118 #
119 # Substitute a regular expression which matches no version
120 # or extension names for None or the empty string.
121 def emptyRegex(self,pat):
122 if (pat == None or pat == ''):
123 return '_nomatch_^'
124 else:
125 return pat
126
127# CGeneratorOptions - subclass of GeneratorOptions.
128#
129# Adds options used by COutputGenerator objects during C language header
130# generation.
131#
132# Additional members
133# prefixText - list of strings to prefix generated header with
134# (usually a copyright statement + calling convention macros).
135# protectFile - True if multiple inclusion protection should be
136# generated (based on the filename) around the entire header.
137# protectFeature - True if #ifndef..#endif protection should be
138# generated around a feature interface in the header file.
139# genFuncPointers - True if function pointer typedefs should be
140# generated
141# protectProto - If conditional protection should be generated
142# around prototype declarations, set to either '#ifdef'
143# to require opt-in (#ifdef protectProtoStr) or '#ifndef'
144# to require opt-out (#ifndef protectProtoStr). Otherwise
145# set to None.
146# protectProtoStr - #ifdef/#ifndef symbol to use around prototype
147# declarations, if protectProto is set
148# apicall - string to use for the function declaration prefix,
149# such as APICALL on Windows.
150# apientry - string to use for the calling convention macro,
151# in typedefs, such as APIENTRY.
152# apientryp - string to use for the calling convention macro
153# in function pointer typedefs, such as APIENTRYP.
154# indentFuncProto - True if prototype declarations should put each
155# parameter on a separate line
156# indentFuncPointer - True if typedefed function pointers should put each
157# parameter on a separate line
158# alignFuncParam - if nonzero and parameters are being put on a
159# separate line, align parameter names at the specified column
160class CGeneratorOptions(GeneratorOptions):
161 """Represents options during C interface generation for headers"""
162 def __init__(self,
163 filename = None,
164 apiname = None,
165 profile = None,
166 versions = '.*',
167 emitversions = '.*',
168 defaultExtensions = None,
169 addExtensions = None,
170 removeExtensions = None,
171 sortProcedure = regSortFeatures,
172 prefixText = "",
173 genFuncPointers = True,
174 protectFile = True,
175 protectFeature = True,
176 protectProto = None,
177 protectProtoStr = None,
178 apicall = '',
179 apientry = '',
180 apientryp = '',
181 indentFuncProto = True,
182 indentFuncPointer = False,
183 alignFuncParam = 0):
184 GeneratorOptions.__init__(self, filename, apiname, profile,
185 versions, emitversions, defaultExtensions,
186 addExtensions, removeExtensions, sortProcedure)
187 self.prefixText = prefixText
188 self.genFuncPointers = genFuncPointers
189 self.protectFile = protectFile
190 self.protectFeature = protectFeature
191 self.protectProto = protectProto
192 self.protectProtoStr = protectProtoStr
193 self.apicall = apicall
194 self.apientry = apientry
195 self.apientryp = apientryp
196 self.indentFuncProto = indentFuncProto
197 self.indentFuncPointer = indentFuncPointer
198 self.alignFuncParam = alignFuncParam
199
200# DocGeneratorOptions - subclass of GeneratorOptions.
201#
202# Shares many members with CGeneratorOptions, since
203# both are writing C-style declarations:
204#
205# prefixText - list of strings to prefix generated header with
206# (usually a copyright statement + calling convention macros).
207# apicall - string to use for the function declaration prefix,
208# such as APICALL on Windows.
209# apientry - string to use for the calling convention macro,
210# in typedefs, such as APIENTRY.
211# apientryp - string to use for the calling convention macro
212# in function pointer typedefs, such as APIENTRYP.
213# genDirectory - directory into which to generate include files
214# indentFuncProto - True if prototype declarations should put each
215# parameter on a separate line
216# indentFuncPointer - True if typedefed function pointers should put each
217# parameter on a separate line
218# alignFuncParam - if nonzero and parameters are being put on a
219# separate line, align parameter names at the specified column
220#
221# Additional members:
222#
223class DocGeneratorOptions(GeneratorOptions):
224 """Represents options during C interface generation for Asciidoc"""
225 def __init__(self,
226 filename = None,
227 apiname = None,
228 profile = None,
229 versions = '.*',
230 emitversions = '.*',
231 defaultExtensions = None,
232 addExtensions = None,
233 removeExtensions = None,
234 sortProcedure = regSortFeatures,
235 prefixText = "",
236 apicall = '',
237 apientry = '',
238 apientryp = '',
239 genDirectory = 'gen',
240 indentFuncProto = True,
241 indentFuncPointer = False,
242 alignFuncParam = 0,
243 expandEnumerants = True):
244 GeneratorOptions.__init__(self, filename, apiname, profile,
245 versions, emitversions, defaultExtensions,
246 addExtensions, removeExtensions, sortProcedure)
247 self.prefixText = prefixText
248 self.apicall = apicall
249 self.apientry = apientry
250 self.apientryp = apientryp
251 self.genDirectory = genDirectory
252 self.indentFuncProto = indentFuncProto
253 self.indentFuncPointer = indentFuncPointer
254 self.alignFuncParam = alignFuncParam
255 self.expandEnumerants = expandEnumerants
256
Mike Stroyan8849f9a2015-11-02 15:30:20 -0700257# ThreadGeneratorOptions - subclass of GeneratorOptions.
258#
259# Adds options used by COutputGenerator objects during C language header
260# generation.
261#
262# Additional members
263# prefixText - list of strings to prefix generated header with
264# (usually a copyright statement + calling convention macros).
265# protectFile - True if multiple inclusion protection should be
266# generated (based on the filename) around the entire header.
267# protectFeature - True if #ifndef..#endif protection should be
268# generated around a feature interface in the header file.
269# genFuncPointers - True if function pointer typedefs should be
270# generated
271# protectProto - True if #ifdef..#endif protection should be
272# generated around prototype declarations
273# protectProtoStr - #ifdef symbol to use around prototype
274# declarations, if protected
275# apicall - string to use for the function declaration prefix,
276# such as APICALL on Windows.
277# apientry - string to use for the calling convention macro,
278# in typedefs, such as APIENTRY.
279# apientryp - string to use for the calling convention macro
280# in function pointer typedefs, such as APIENTRYP.
281# indentFuncProto - True if prototype declarations should put each
282# parameter on a separate line
283# indentFuncPointer - True if typedefed function pointers should put each
284# parameter on a separate line
285# alignFuncParam - if nonzero and parameters are being put on a
286# separate line, align parameter names at the specified column
287class ThreadGeneratorOptions(GeneratorOptions):
288 """Represents options during C interface generation for headers"""
289 def __init__(self,
290 filename = None,
291 apiname = None,
292 profile = None,
293 versions = '.*',
294 emitversions = '.*',
295 defaultExtensions = None,
296 addExtensions = None,
297 removeExtensions = None,
298 sortProcedure = regSortFeatures,
299 prefixText = "",
300 genFuncPointers = True,
301 protectFile = True,
302 protectFeature = True,
303 protectProto = True,
304 protectProtoStr = True,
305 apicall = '',
306 apientry = '',
307 apientryp = '',
308 indentFuncProto = True,
309 indentFuncPointer = False,
310 alignFuncParam = 0):
311 GeneratorOptions.__init__(self, filename, apiname, profile,
312 versions, emitversions, defaultExtensions,
313 addExtensions, removeExtensions, sortProcedure)
314 self.prefixText = prefixText
315 self.genFuncPointers = genFuncPointers
316 self.protectFile = protectFile
317 self.protectFeature = protectFeature
318 self.protectProto = protectProto
319 self.protectProtoStr = protectProtoStr
320 self.apicall = apicall
321 self.apientry = apientry
322 self.apientryp = apientryp
323 self.indentFuncProto = indentFuncProto
324 self.indentFuncPointer = indentFuncPointer
325 self.alignFuncParam = alignFuncParam
326
327
Dustin Gravesf69e3772016-02-11 10:10:14 -0700328# ParamCheckerGeneratorOptions - subclass of GeneratorOptions.
329#
330# Adds options used by ParamCheckerOutputGenerator objects during param checker
331# generation.
332#
333# Additional members
334# prefixText - list of strings to prefix generated header with
335# (usually a copyright statement + calling convention macros).
336# protectFile - True if multiple inclusion protection should be
337# generated (based on the filename) around the entire header.
338# protectFeature - True if #ifndef..#endif protection should be
339# generated around a feature interface in the header file.
340# genFuncPointers - True if function pointer typedefs should be
341# generated
342# protectProto - If conditional protection should be generated
343# around prototype declarations, set to either '#ifdef'
344# to require opt-in (#ifdef protectProtoStr) or '#ifndef'
345# to require opt-out (#ifndef protectProtoStr). Otherwise
346# set to None.
347# protectProtoStr - #ifdef/#ifndef symbol to use around prototype
348# declarations, if protectProto is set
349# apicall - string to use for the function declaration prefix,
350# such as APICALL on Windows.
351# apientry - string to use for the calling convention macro,
352# in typedefs, such as APIENTRY.
353# apientryp - string to use for the calling convention macro
354# in function pointer typedefs, such as APIENTRYP.
355# indentFuncProto - True if prototype declarations should put each
356# parameter on a separate line
357# indentFuncPointer - True if typedefed function pointers should put each
358# parameter on a separate line
359# alignFuncParam - if nonzero and parameters are being put on a
360# separate line, align parameter names at the specified column
361class ParamCheckerGeneratorOptions(GeneratorOptions):
362 """Represents options during C interface generation for headers"""
363 def __init__(self,
364 filename = None,
365 apiname = None,
366 profile = None,
367 versions = '.*',
368 emitversions = '.*',
369 defaultExtensions = None,
370 addExtensions = None,
371 removeExtensions = None,
372 sortProcedure = regSortFeatures,
373 prefixText = "",
374 genFuncPointers = True,
375 protectFile = True,
376 protectFeature = True,
377 protectProto = None,
378 protectProtoStr = None,
379 apicall = '',
380 apientry = '',
381 apientryp = '',
382 indentFuncProto = True,
383 indentFuncPointer = False,
384 alignFuncParam = 0):
385 GeneratorOptions.__init__(self, filename, apiname, profile,
386 versions, emitversions, defaultExtensions,
387 addExtensions, removeExtensions, sortProcedure)
388 self.prefixText = prefixText
389 self.genFuncPointers = genFuncPointers
390 self.protectFile = protectFile
391 self.protectFeature = protectFeature
392 self.protectProto = protectProto
393 self.protectProtoStr = protectProtoStr
394 self.apicall = apicall
395 self.apientry = apientry
396 self.apientryp = apientryp
397 self.indentFuncProto = indentFuncProto
398 self.indentFuncPointer = indentFuncPointer
399 self.alignFuncParam = alignFuncParam
400
401
Mike Stroyana451fa82016-01-07 15:35:37 -0700402# OutputGenerator - base class for generating API interfaces.
403# Manages basic logic, logging, and output file control
404# Derived classes actually generate formatted output.
405#
406# ---- methods ----
407# OutputGenerator(errFile, warnFile, diagFile)
408# errFile, warnFile, diagFile - file handles to write errors,
409# warnings, diagnostics to. May be None to not write.
410# logMsg(level, *args) - log messages of different categories
411# level - 'error', 'warn', or 'diag'. 'error' will also
412# raise a UserWarning exception
413# *args - print()-style arguments
414# setExtMap(map) - specify a dictionary map from extension names to
415# numbers, used in creating values for extension enumerants.
416# beginFile(genOpts) - start a new interface file
417# genOpts - GeneratorOptions controlling what's generated and how
418# endFile() - finish an interface file, closing it when done
419# beginFeature(interface, emit) - write interface for a feature
420# and tag generated features as having been done.
421# interface - element for the <version> / <extension> to generate
422# emit - actually write to the header only when True
423# endFeature() - finish an interface.
424# genType(typeinfo,name) - generate interface for a type
425# typeinfo - TypeInfo for a type
426# genStruct(typeinfo,name) - generate interface for a C "struct" type.
427# typeinfo - TypeInfo for a type interpreted as a struct
428# genGroup(groupinfo,name) - generate interface for a group of enums (C "enum")
429# groupinfo - GroupInfo for a group
430# genEnum(enuminfo, name) - generate interface for an enum (constant)
431# enuminfo - EnumInfo for an enum
432# name - enum name
433# genCmd(cmdinfo) - generate interface for a command
434# cmdinfo - CmdInfo for a command
435# makeCDecls(cmd) - return C prototype and function pointer typedef for a
436# <command> Element, as a list of two strings
437# cmd - Element for the <command>
438# newline() - print a newline to the output file (utility function)
439#
440class OutputGenerator:
441 """Generate specified API interfaces in a specific style, such as a C header"""
442 def __init__(self,
443 errFile = sys.stderr,
444 warnFile = sys.stderr,
445 diagFile = sys.stdout):
446 self.outFile = None
447 self.errFile = errFile
448 self.warnFile = warnFile
449 self.diagFile = diagFile
450 # Internal state
451 self.featureName = None
452 self.genOpts = None
453 self.registry = None
454 # Used for extension enum value generation
455 self.extBase = 1000000000
456 self.extBlockSize = 1000
457 #
458 # logMsg - write a message of different categories to different
459 # destinations.
460 # level -
461 # 'diag' (diagnostic, voluminous)
462 # 'warn' (warning)
463 # 'error' (fatal error - raises exception after logging)
464 # *args - print()-style arguments to direct to corresponding log
465 def logMsg(self, level, *args):
466 """Log a message at the given level. Can be ignored or log to a file"""
467 if (level == 'error'):
468 strfile = io.StringIO()
469 write('ERROR:', *args, file=strfile)
470 if (self.errFile != None):
471 write(strfile.getvalue(), file=self.errFile)
472 raise UserWarning(strfile.getvalue())
473 elif (level == 'warn'):
474 if (self.warnFile != None):
475 write('WARNING:', *args, file=self.warnFile)
476 elif (level == 'diag'):
477 if (self.diagFile != None):
478 write('DIAG:', *args, file=self.diagFile)
479 else:
480 raise UserWarning(
481 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
482 #
483 # enumToValue - parses and converts an <enum> tag into a value.
484 # Returns a list
485 # first element - integer representation of the value, or None
486 # if needsNum is False. The value must be a legal number
487 # if needsNum is True.
488 # second element - string representation of the value
489 # There are several possible representations of values.
490 # A 'value' attribute simply contains the value.
491 # A 'bitpos' attribute defines a value by specifying the bit
492 # position which is set in that value.
493 # A 'offset','extbase','extends' triplet specifies a value
494 # as an offset to a base value defined by the specified
495 # 'extbase' extension name, which is then cast to the
496 # typename specified by 'extends'. This requires probing
497 # the registry database, and imbeds knowledge of the
498 # Vulkan extension enum scheme in this function.
499 def enumToValue(self, elem, needsNum):
500 name = elem.get('name')
501 numVal = None
502 if ('value' in elem.keys()):
503 value = elem.get('value')
504 # print('About to translate value =', value, 'type =', type(value))
505 if (needsNum):
506 numVal = int(value, 0)
507 # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or
508 # 'ull'), append it to the string value.
509 # t = enuminfo.elem.get('type')
510 # if (t != None and t != '' and t != 'i' and t != 's'):
511 # value += enuminfo.type
512 self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
513 return [numVal, value]
514 if ('bitpos' in elem.keys()):
515 value = elem.get('bitpos')
516 numVal = int(value, 0)
517 numVal = 1 << numVal
518 value = '0x%08x' % numVal
519 self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
520 return [numVal, value]
521 if ('offset' in elem.keys()):
522 # Obtain values in the mapping from the attributes
523 enumNegative = False
524 offset = int(elem.get('offset'),0)
525 extnumber = int(elem.get('extnumber'),0)
526 extends = elem.get('extends')
527 if ('dir' in elem.keys()):
528 enumNegative = True
529 self.logMsg('diag', 'Enum', name, 'offset =', offset,
530 'extnumber =', extnumber, 'extends =', extends,
531 'enumNegative =', enumNegative)
532 # Now determine the actual enumerant value, as defined
533 # in the "Layers and Extensions" appendix of the spec.
534 numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
535 if (enumNegative):
536 numVal = -numVal
537 value = '%d' % numVal
538 # More logic needed!
539 self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
540 return [numVal, value]
541 return [None, None]
542 #
543 def beginFile(self, genOpts):
544 self.genOpts = genOpts
545 #
546 # Open specified output file. Not done in constructor since a
547 # Generator can be used without writing to a file.
548 if (self.genOpts.filename != None):
549 self.outFile = open(self.genOpts.filename, 'w')
550 else:
551 self.outFile = sys.stdout
552 def endFile(self):
553 self.errFile and self.errFile.flush()
554 self.warnFile and self.warnFile.flush()
555 self.diagFile and self.diagFile.flush()
556 self.outFile.flush()
557 if (self.outFile != sys.stdout and self.outFile != sys.stderr):
558 self.outFile.close()
559 self.genOpts = None
560 #
561 def beginFeature(self, interface, emit):
562 self.emit = emit
563 self.featureName = interface.get('name')
564 # If there's an additional 'protect' attribute in the feature, save it
565 self.featureExtraProtect = interface.get('protect')
566 def endFeature(self):
567 # Derived classes responsible for emitting feature
568 self.featureName = None
569 self.featureExtraProtect = None
570 # Utility method to validate we're generating something only inside a
571 # <feature> tag
572 def validateFeature(self, featureType, featureName):
573 if (self.featureName == None):
574 raise UserWarning('Attempt to generate', featureType, name,
575 'when not in feature')
576 #
577 # Type generation
578 def genType(self, typeinfo, name):
579 self.validateFeature('type', name)
580 #
581 # Struct (e.g. C "struct" type) generation
582 def genStruct(self, typeinfo, name):
583 self.validateFeature('struct', name)
584 #
585 # Group (e.g. C "enum" type) generation
586 def genGroup(self, groupinfo, name):
587 self.validateFeature('group', name)
588 #
589 # Enumerant (really, constant) generation
590 def genEnum(self, enuminfo, name):
591 self.validateFeature('enum', name)
592 #
593 # Command generation
594 def genCmd(self, cmd, name):
595 self.validateFeature('command', name)
596 #
597 # Utility functions - turn a <proto> <name> into C-language prototype
598 # and typedef declarations for that name.
599 # name - contents of <name> tag
600 # tail - whatever text follows that tag in the Element
601 def makeProtoName(self, name, tail):
602 return self.genOpts.apientry + name + tail
603 def makeTypedefName(self, name, tail):
604 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
605 #
606 # makeCParamDecl - return a string which is an indented, formatted
607 # declaration for a <param> or <member> block (e.g. function parameter
608 # or structure/union member).
609 # param - Element (<param> or <member>) to format
610 # aligncol - if non-zero, attempt to align the nested <name> element
611 # at this column
612 def makeCParamDecl(self, param, aligncol):
613 paramdecl = ' ' + noneStr(param.text)
614 for elem in param:
615 text = noneStr(elem.text)
616 tail = noneStr(elem.tail)
617 if (elem.tag == 'name' and aligncol > 0):
618 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
619 # Align at specified column, if possible
620 paramdecl = paramdecl.rstrip()
621 oldLen = len(paramdecl)
622 paramdecl = paramdecl.ljust(aligncol)
623 newLen = len(paramdecl)
624 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
625 paramdecl += text + tail
626 return paramdecl
627 #
628 # getCParamTypeLength - return the length of the type field is an indented, formatted
629 # declaration for a <param> or <member> block (e.g. function parameter
630 # or structure/union member).
631 # param - Element (<param> or <member>) to identify
632 def getCParamTypeLength(self, param):
633 paramdecl = ' ' + noneStr(param.text)
634 for elem in param:
635 text = noneStr(elem.text)
636 tail = noneStr(elem.tail)
637 if (elem.tag == 'name'):
638 # Align at specified column, if possible
639 newLen = len(paramdecl.rstrip())
640 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
641 paramdecl += text + tail
642 return newLen
643 #
644 # makeCDecls - return C prototype and function pointer typedef for a
645 # command, as a two-element list of strings.
646 # cmd - Element containing a <command> tag
647 def makeCDecls(self, cmd):
648 """Generate C function pointer typedef for <command> Element"""
649 proto = cmd.find('proto')
650 params = cmd.findall('param')
651 # Begin accumulating prototype and typedef strings
652 pdecl = self.genOpts.apicall
653 tdecl = 'typedef '
654 #
655 # Insert the function return type/name.
656 # For prototypes, add APIENTRY macro before the name
657 # For typedefs, add (APIENTRY *<name>) around the name and
658 # use the PFN_cmdnameproc naming convention.
659 # Done by walking the tree for <proto> element by element.
660 # lxml.etree has elem.text followed by (elem[i], elem[i].tail)
661 # for each child element and any following text
662 # Leading text
663 pdecl += noneStr(proto.text)
664 tdecl += noneStr(proto.text)
665 # For each child element, if it's a <name> wrap in appropriate
666 # declaration. Otherwise append its contents and tail contents.
667 for elem in proto:
668 text = noneStr(elem.text)
669 tail = noneStr(elem.tail)
670 if (elem.tag == 'name'):
671 pdecl += self.makeProtoName(text, tail)
672 tdecl += self.makeTypedefName(text, tail)
673 else:
674 pdecl += text + tail
675 tdecl += text + tail
676 # Now add the parameter declaration list, which is identical
677 # for prototypes and typedefs. Concatenate all the text from
678 # a <param> node without the tags. No tree walking required
679 # since all tags are ignored.
680 # Uses: self.indentFuncProto
681 # self.indentFuncPointer
682 # self.alignFuncParam
683 # Might be able to doubly-nest the joins, e.g.
684 # ','.join(('_'.join([l[i] for i in range(0,len(l))])
685 n = len(params)
686 # Indented parameters
687 if n > 0:
688 indentdecl = '(\n'
689 for i in range(0,n):
690 paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam)
691 if (i < n - 1):
692 paramdecl += ',\n'
693 else:
694 paramdecl += ');'
695 indentdecl += paramdecl
696 else:
697 indentdecl = '(void);'
698 # Non-indented parameters
699 paramdecl = '('
700 if n > 0:
701 for i in range(0,n):
702 paramdecl += ''.join([t for t in params[i].itertext()])
703 if (i < n - 1):
704 paramdecl += ', '
705 else:
706 paramdecl += 'void'
707 paramdecl += ");";
708 return [ pdecl + indentdecl, tdecl + paramdecl ]
709 #
710 def newline(self):
711 write('', file=self.outFile)
712
713 def setRegistry(self, registry):
714 self.registry = registry
715 #
716
717# COutputGenerator - subclass of OutputGenerator.
718# Generates C-language API interfaces.
719#
720# ---- methods ----
721# COutputGenerator(errFile, warnFile, diagFile) - args as for
722# OutputGenerator. Defines additional internal state.
723# ---- methods overriding base class ----
724# beginFile(genOpts)
725# endFile()
726# beginFeature(interface, emit)
727# endFeature()
728# genType(typeinfo,name)
729# genStruct(typeinfo,name)
730# genGroup(groupinfo,name)
731# genEnum(enuminfo, name)
732# genCmd(cmdinfo)
733class COutputGenerator(OutputGenerator):
734 """Generate specified API interfaces in a specific style, such as a C header"""
735 # This is an ordered list of sections in the header file.
736 TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
737 'group', 'bitmask', 'funcpointer', 'struct']
738 ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command']
739 def __init__(self,
740 errFile = sys.stderr,
741 warnFile = sys.stderr,
742 diagFile = sys.stdout):
743 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
744 # Internal state - accumulators for different inner block text
745 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
746 #
747 def beginFile(self, genOpts):
748 OutputGenerator.beginFile(self, genOpts)
749 # C-specific
750 #
751 # Multiple inclusion protection & C++ wrappers.
752 if (genOpts.protectFile and self.genOpts.filename):
753 headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
754 write('#ifndef', headerSym, file=self.outFile)
755 write('#define', headerSym, '1', file=self.outFile)
756 self.newline()
757 write('#ifdef __cplusplus', file=self.outFile)
758 write('extern "C" {', file=self.outFile)
759 write('#endif', file=self.outFile)
760 self.newline()
761 #
762 # User-supplied prefix text, if any (list of strings)
763 if (genOpts.prefixText):
764 for s in genOpts.prefixText:
765 write(s, file=self.outFile)
766 #
767 # Some boilerplate describing what was generated - this
768 # will probably be removed later since the extensions
769 # pattern may be very long.
770 # write('/* Generated C header for:', file=self.outFile)
771 # write(' * API:', genOpts.apiname, file=self.outFile)
772 # if (genOpts.profile):
773 # write(' * Profile:', genOpts.profile, file=self.outFile)
774 # write(' * Versions considered:', genOpts.versions, file=self.outFile)
775 # write(' * Versions emitted:', genOpts.emitversions, file=self.outFile)
776 # write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile)
777 # write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile)
778 # write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile)
779 # write(' */', file=self.outFile)
780 def endFile(self):
781 # C-specific
782 # Finish C++ wrapper and multiple inclusion protection
783 self.newline()
784 write('#ifdef __cplusplus', file=self.outFile)
785 write('}', file=self.outFile)
786 write('#endif', file=self.outFile)
787 if (self.genOpts.protectFile and self.genOpts.filename):
788 self.newline()
789 write('#endif', file=self.outFile)
790 # Finish processing in superclass
791 OutputGenerator.endFile(self)
792 def beginFeature(self, interface, emit):
793 # Start processing in superclass
794 OutputGenerator.beginFeature(self, interface, emit)
795 # C-specific
796 # Accumulate includes, defines, types, enums, function pointer typedefs,
797 # end function prototypes separately for this feature. They're only
798 # printed in endFeature().
799 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
800 def endFeature(self):
801 # C-specific
802 # Actually write the interface to the output file.
803 if (self.emit):
804 self.newline()
805 if (self.genOpts.protectFeature):
806 write('#ifndef', self.featureName, file=self.outFile)
807 # If type declarations are needed by other features based on
808 # this one, it may be necessary to suppress the ExtraProtect,
809 # or move it below the 'for section...' loop.
810 if (self.featureExtraProtect != None):
811 write('#ifdef', self.featureExtraProtect, file=self.outFile)
812 write('#define', self.featureName, '1', file=self.outFile)
813 for section in self.TYPE_SECTIONS:
814 contents = self.sections[section]
815 if contents:
816 write('\n'.join(contents), file=self.outFile)
817 self.newline()
818 if (self.genOpts.genFuncPointers and self.sections['commandPointer']):
819 write('\n'.join(self.sections['commandPointer']), file=self.outFile)
820 self.newline()
821 if (self.sections['command']):
822 if (self.genOpts.protectProto):
823 write(self.genOpts.protectProto,
824 self.genOpts.protectProtoStr, file=self.outFile)
825 write('\n'.join(self.sections['command']), end='', file=self.outFile)
826 if (self.genOpts.protectProto):
827 write('#endif', file=self.outFile)
828 else:
829 self.newline()
830 if (self.featureExtraProtect != None):
831 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
832 if (self.genOpts.protectFeature):
833 write('#endif /*', self.featureName, '*/', file=self.outFile)
834 # Finish processing in superclass
835 OutputGenerator.endFeature(self)
836 #
837 # Append a definition to the specified section
838 def appendSection(self, section, text):
839 # self.sections[section].append('SECTION: ' + section + '\n')
840 self.sections[section].append(text)
841 #
842 # Type generation
843 def genType(self, typeinfo, name):
844 OutputGenerator.genType(self, typeinfo, name)
845 typeElem = typeinfo.elem
846 # If the type is a struct type, traverse the imbedded <member> tags
847 # generating a structure. Otherwise, emit the tag text.
848 category = typeElem.get('category')
849 if (category == 'struct' or category == 'union'):
850 self.genStruct(typeinfo, name)
851 else:
852 # Replace <apientry /> tags with an APIENTRY-style string
853 # (from self.genOpts). Copy other text through unchanged.
854 # If the resulting text is an empty string, don't emit it.
855 s = noneStr(typeElem.text)
856 for elem in typeElem:
857 if (elem.tag == 'apientry'):
858 s += self.genOpts.apientry + noneStr(elem.tail)
859 else:
860 s += noneStr(elem.text) + noneStr(elem.tail)
861 if s:
862 # Add extra newline after multi-line entries.
863 if '\n' in s:
864 s += '\n'
865 self.appendSection(category, s)
866 #
867 # Struct (e.g. C "struct" type) generation.
868 # This is a special case of the <type> tag where the contents are
869 # interpreted as a set of <member> tags instead of freeform C
870 # C type declarations. The <member> tags are just like <param>
871 # tags - they are a declaration of a struct or union member.
872 # Only simple member declarations are supported (no nested
873 # structs etc.)
874 def genStruct(self, typeinfo, typeName):
875 OutputGenerator.genStruct(self, typeinfo, typeName)
876 body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
877 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
878 targetLen = 0;
879 for member in typeinfo.elem.findall('.//member'):
880 targetLen = max(targetLen, self.getCParamTypeLength(member))
881 for member in typeinfo.elem.findall('.//member'):
882 body += self.makeCParamDecl(member, targetLen + 4)
883 body += ';\n'
884 body += '} ' + typeName + ';\n'
885 self.appendSection('struct', body)
886 #
887 # Group (e.g. C "enum" type) generation.
888 # These are concatenated together with other types.
889 def genGroup(self, groupinfo, groupName):
890 OutputGenerator.genGroup(self, groupinfo, groupName)
891 groupElem = groupinfo.elem
892 # See if this group needs min/max/num/padding at end
893 expand = 'expand' in groupElem.keys()
894 if (expand):
895 expandPrefix = groupElem.get('expand')
896 # Prefix
897 body = "\ntypedef enum " + groupName + " {\n"
898
899 # Loop over the nested 'enum' tags. Keep track of the minimum and
900 # maximum numeric values, if they can be determined; but only for
901 # core API enumerants, not extension enumerants. This is inferred
902 # by looking for 'extends' attributes.
903 minName = None
904 for elem in groupElem.findall('enum'):
905 # Convert the value to an integer and use that to track min/max.
906 # Values of form -(number) are accepted but nothing more complex.
907 # Should catch exceptions here for more complex constructs. Not yet.
908 (numVal,strVal) = self.enumToValue(elem, True)
909 name = elem.get('name')
910 body += " " + name + " = " + strVal + ",\n"
911 if (expand and elem.get('extends') is None):
912 if (minName == None):
913 minName = maxName = name
914 minValue = maxValue = numVal
915 elif (numVal < minValue):
916 minName = name
917 minValue = numVal
918 elif (numVal > maxValue):
919 maxName = name
920 maxValue = numVal
921 # Generate min/max value tokens and a range-padding enum. Need some
922 # additional padding to generate correct names...
923 if (expand):
924 body += " " + expandPrefix + "_BEGIN_RANGE = " + minName + ",\n"
925 body += " " + expandPrefix + "_END_RANGE = " + maxName + ",\n"
926 body += " " + expandPrefix + "_RANGE_SIZE = (" + maxName + " - " + minName + " + 1),\n"
927 body += " " + expandPrefix + "_MAX_ENUM = 0x7FFFFFFF\n"
928 # Postfix
929 body += "} " + groupName + ";"
930 if groupElem.get('type') == 'bitmask':
931 section = 'bitmask'
932 else:
933 section = 'group'
934 self.appendSection(section, body)
935 # Enumerant generation
936 # <enum> tags may specify their values in several ways, but are usually
937 # just integers.
938 def genEnum(self, enuminfo, name):
939 OutputGenerator.genEnum(self, enuminfo, name)
940 (numVal,strVal) = self.enumToValue(enuminfo.elem, False)
941 body = '#define ' + name.ljust(33) + ' ' + strVal
942 self.appendSection('enum', body)
943 #
944 # Command generation
945 def genCmd(self, cmdinfo, name):
946 OutputGenerator.genCmd(self, cmdinfo, name)
947 #
948 decls = self.makeCDecls(cmdinfo.elem)
949 self.appendSection('command', decls[0] + '\n')
950 if (self.genOpts.genFuncPointers):
951 self.appendSection('commandPointer', decls[1])
952
953# DocOutputGenerator - subclass of OutputGenerator.
954# Generates AsciiDoc includes with C-language API interfaces, for reference
955# pages and the Vulkan specification. Similar to COutputGenerator, but
956# each interface is written into a different file as determined by the
957# options, only actual C types are emitted, and none of the boilerplate
958# preprocessor code is emitted.
959#
960# ---- methods ----
961# DocOutputGenerator(errFile, warnFile, diagFile) - args as for
962# OutputGenerator. Defines additional internal state.
963# ---- methods overriding base class ----
964# beginFile(genOpts)
965# endFile()
966# beginFeature(interface, emit)
967# endFeature()
968# genType(typeinfo,name)
969# genStruct(typeinfo,name)
970# genGroup(groupinfo,name)
971# genEnum(enuminfo, name)
972# genCmd(cmdinfo)
973class DocOutputGenerator(OutputGenerator):
974 """Generate specified API interfaces in a specific style, such as a C header"""
975 def __init__(self,
976 errFile = sys.stderr,
977 warnFile = sys.stderr,
978 diagFile = sys.stdout):
979 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
980 #
981 def beginFile(self, genOpts):
982 OutputGenerator.beginFile(self, genOpts)
983 def endFile(self):
984 OutputGenerator.endFile(self)
985 def beginFeature(self, interface, emit):
986 # Start processing in superclass
987 OutputGenerator.beginFeature(self, interface, emit)
988 def endFeature(self):
989 # Finish processing in superclass
990 OutputGenerator.endFeature(self)
991 #
992 # Generate an include file
993 #
994 # directory - subdirectory to put file in
995 # basename - base name of the file
996 # contents - contents of the file (Asciidoc boilerplate aside)
997 def writeInclude(self, directory, basename, contents):
998 # Create file
999 filename = self.genOpts.genDirectory + '/' + directory + '/' + basename + '.txt'
1000 self.logMsg('diag', '# Generating include file:', filename)
1001 fp = open(filename, 'w')
1002 # Asciidoc anchor
1003 write('[[{0},{0}]]'.format(basename), file=fp)
1004 write('["source","{basebackend@docbook:c++:cpp}",title=""]', file=fp)
1005 write('------------------------------------------------------------------------------', file=fp)
1006 write(contents, file=fp)
1007 write('------------------------------------------------------------------------------', file=fp)
1008 fp.close()
1009 #
1010 # Type generation
1011 def genType(self, typeinfo, name):
1012 OutputGenerator.genType(self, typeinfo, name)
1013 typeElem = typeinfo.elem
1014 # If the type is a struct type, traverse the imbedded <member> tags
1015 # generating a structure. Otherwise, emit the tag text.
1016 category = typeElem.get('category')
1017 if (category == 'struct' or category == 'union'):
1018 self.genStruct(typeinfo, name)
1019 else:
1020 # Replace <apientry /> tags with an APIENTRY-style string
1021 # (from self.genOpts). Copy other text through unchanged.
1022 # If the resulting text is an empty string, don't emit it.
1023 s = noneStr(typeElem.text)
1024 for elem in typeElem:
1025 if (elem.tag == 'apientry'):
1026 s += self.genOpts.apientry + noneStr(elem.tail)
1027 else:
1028 s += noneStr(elem.text) + noneStr(elem.tail)
1029 if (len(s) > 0):
1030 if (category == 'bitmask'):
1031 self.writeInclude('flags', name, s + '\n')
1032 elif (category == 'enum'):
1033 self.writeInclude('enums', name, s + '\n')
1034 elif (category == 'funcpointer'):
1035 self.writeInclude('funcpointers', name, s+ '\n')
1036 else:
1037 self.logMsg('diag', '# NOT writing include file for type:',
1038 name, 'category: ', category)
1039 else:
1040 self.logMsg('diag', '# NOT writing empty include file for type', name)
1041 #
1042 # Struct (e.g. C "struct" type) generation.
1043 # This is a special case of the <type> tag where the contents are
1044 # interpreted as a set of <member> tags instead of freeform C
1045 # C type declarations. The <member> tags are just like <param>
1046 # tags - they are a declaration of a struct or union member.
1047 # Only simple member declarations are supported (no nested
1048 # structs etc.)
1049 def genStruct(self, typeinfo, typeName):
1050 OutputGenerator.genStruct(self, typeinfo, typeName)
1051 s = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
1052 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
1053 targetLen = 0;
1054 for member in typeinfo.elem.findall('.//member'):
1055 targetLen = max(targetLen, self.getCParamTypeLength(member))
1056 for member in typeinfo.elem.findall('.//member'):
1057 s += self.makeCParamDecl(member, targetLen + 4)
1058 s += ';\n'
1059 s += '} ' + typeName + ';'
1060 self.writeInclude('structs', typeName, s)
1061 #
1062 # Group (e.g. C "enum" type) generation.
1063 # These are concatenated together with other types.
1064 def genGroup(self, groupinfo, groupName):
1065 OutputGenerator.genGroup(self, groupinfo, groupName)
1066 groupElem = groupinfo.elem
1067 # See if this group needs min/max/num/padding at end
1068 expand = self.genOpts.expandEnumerants and ('expand' in groupElem.keys())
1069 if (expand):
1070 expandPrefix = groupElem.get('expand')
1071 # Prefix
1072 s = "typedef enum " + groupName + " {\n"
1073
1074 # Loop over the nested 'enum' tags. Keep track of the minimum and
1075 # maximum numeric values, if they can be determined.
1076 minName = None
1077 for elem in groupElem.findall('enum'):
1078 # Convert the value to an integer and use that to track min/max.
1079 # Values of form -(number) are accepted but nothing more complex.
1080 # Should catch exceptions here for more complex constructs. Not yet.
1081 (numVal,strVal) = self.enumToValue(elem, True)
1082 name = elem.get('name')
1083 s += " " + name + " = " + strVal + ",\n"
1084 if (expand and elem.get('extends') is None):
1085 if (minName == None):
1086 minName = maxName = name
1087 minValue = maxValue = numVal
1088 elif (numVal < minValue):
1089 minName = name
1090 minValue = numVal
1091 elif (numVal > maxValue):
1092 maxName = name
1093 maxValue = numVal
1094 # Generate min/max value tokens and a range-padding enum. Need some
1095 # additional padding to generate correct names...
1096 if (expand):
1097 s += "\n"
1098 s += " " + expandPrefix + "_BEGIN_RANGE = " + minName + ",\n"
1099 s += " " + expandPrefix + "_END_RANGE = " + maxName + ",\n"
1100 s += " " + expandPrefix + "_NUM = (" + maxName + " - " + minName + " + 1),\n"
1101 s += " " + expandPrefix + "_MAX_ENUM = 0x7FFFFFFF\n"
1102 # Postfix
1103 s += "} " + groupName + ";"
1104 self.writeInclude('enums', groupName, s)
1105 # Enumerant generation
1106 # <enum> tags may specify their values in several ways, but are usually
1107 # just integers.
1108 def genEnum(self, enuminfo, name):
1109 OutputGenerator.genEnum(self, enuminfo, name)
1110 (numVal,strVal) = self.enumToValue(enuminfo.elem, False)
1111 s = '#define ' + name.ljust(33) + ' ' + strVal
1112 self.logMsg('diag', '# NOT writing compile-time constant', name)
1113 # self.writeInclude('consts', name, s)
1114 #
1115 # Command generation
1116 def genCmd(self, cmdinfo, name):
1117 OutputGenerator.genCmd(self, cmdinfo, name)
1118 #
1119 decls = self.makeCDecls(cmdinfo.elem)
1120 self.writeInclude('protos', name, decls[0])
1121
1122# PyOutputGenerator - subclass of OutputGenerator.
1123# Generates Python data structures describing API names.
1124# Similar to DocOutputGenerator, but writes a single
1125# file.
1126#
1127# ---- methods ----
1128# PyOutputGenerator(errFile, warnFile, diagFile) - args as for
1129# OutputGenerator. Defines additional internal state.
1130# ---- methods overriding base class ----
1131# beginFile(genOpts)
1132# endFile()
1133# genType(typeinfo,name)
1134# genStruct(typeinfo,name)
1135# genGroup(groupinfo,name)
1136# genEnum(enuminfo, name)
1137# genCmd(cmdinfo)
1138class PyOutputGenerator(OutputGenerator):
1139 """Generate specified API interfaces in a specific style, such as a C header"""
1140 def __init__(self,
1141 errFile = sys.stderr,
1142 warnFile = sys.stderr,
1143 diagFile = sys.stdout):
1144 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
1145 #
1146 def beginFile(self, genOpts):
1147 OutputGenerator.beginFile(self, genOpts)
1148 for dict in [ 'flags', 'enums', 'structs', 'consts', 'enums',
1149 'consts', 'protos', 'funcpointers' ]:
1150 write(dict, '= {}', file=self.outFile)
1151 def endFile(self):
1152 OutputGenerator.endFile(self)
1153 #
1154 # Add a name from the interface
1155 #
1156 # dict - type of name (see beginFile above)
1157 # name - name to add
1158 # value - A serializable Python value for the name
1159 def addName(self, dict, name, value=None):
1160 write(dict + "['" + name + "'] = ", value, file=self.outFile)
1161 #
1162 # Type generation
1163 # For 'struct' or 'union' types, defer to genStruct() to
1164 # add to the dictionary.
1165 # For 'bitmask' types, add the type name to the 'flags' dictionary,
1166 # with the value being the corresponding 'enums' name defining
1167 # the acceptable flag bits.
1168 # For 'enum' types, add the type name to the 'enums' dictionary,
1169 # with the value being '@STOPHERE@' (because this case seems
1170 # never to happen).
1171 # For 'funcpointer' types, add the type name to the 'funcpointers'
1172 # dictionary.
1173 # For 'handle' and 'define' types, add the handle or #define name
1174 # to the 'struct' dictionary, because that's how the spec sources
1175 # tag these types even though they aren't structs.
1176 def genType(self, typeinfo, name):
1177 OutputGenerator.genType(self, typeinfo, name)
1178 typeElem = typeinfo.elem
1179 # If the type is a struct type, traverse the imbedded <member> tags
1180 # generating a structure. Otherwise, emit the tag text.
1181 category = typeElem.get('category')
1182 if (category == 'struct' or category == 'union'):
1183 self.genStruct(typeinfo, name)
1184 else:
1185 # Extract the type name
1186 # (from self.genOpts). Copy other text through unchanged.
1187 # If the resulting text is an empty string, don't emit it.
1188 count = len(noneStr(typeElem.text))
1189 for elem in typeElem:
1190 count += len(noneStr(elem.text)) + len(noneStr(elem.tail))
1191 if (count > 0):
1192 if (category == 'bitmask'):
1193 requiredEnum = typeElem.get('requires')
1194 self.addName('flags', name, enquote(requiredEnum))
1195 elif (category == 'enum'):
1196 # This case never seems to come up!
1197 # @enums C 'enum' name Dictionary of enumerant names
1198 self.addName('enums', name, enquote('@STOPHERE@'))
1199 elif (category == 'funcpointer'):
1200 self.addName('funcpointers', name, None)
1201 elif (category == 'handle' or category == 'define'):
1202 self.addName('structs', name, None)
1203 else:
1204 write('# Unprocessed type:', name, 'category:', category, file=self.outFile)
1205 else:
1206 write('# Unprocessed type:', name, file=self.outFile)
1207 #
1208 # Struct (e.g. C "struct" type) generation.
1209 #
1210 # Add the struct name to the 'structs' dictionary, with the
1211 # value being an ordered list of the struct member names.
1212 def genStruct(self, typeinfo, typeName):
1213 OutputGenerator.genStruct(self, typeinfo, typeName)
1214
1215 members = [member.text for member in typeinfo.elem.findall('.//member/name')]
1216 self.addName('structs', typeName, members)
1217 #
1218 # Group (e.g. C "enum" type) generation.
1219 # These are concatenated together with other types.
1220 #
1221 # Add the enum type name to the 'enums' dictionary, with
1222 # the value being an ordered list of the enumerant names.
1223 # Add each enumerant name to the 'consts' dictionary, with
1224 # the value being the enum type the enumerant is part of.
1225 def genGroup(self, groupinfo, groupName):
1226 OutputGenerator.genGroup(self, groupinfo, groupName)
1227 groupElem = groupinfo.elem
1228
1229 # @enums C 'enum' name Dictionary of enumerant names
1230 # @consts C enumerant/const name Name of corresponding 'enums' key
1231
1232 # Loop over the nested 'enum' tags. Keep track of the minimum and
1233 # maximum numeric values, if they can be determined.
1234 enumerants = [elem.get('name') for elem in groupElem.findall('enum')]
1235 for name in enumerants:
1236 self.addName('consts', name, enquote(groupName))
1237 self.addName('enums', groupName, enumerants)
1238 # Enumerant generation (compile-time constants)
1239 #
1240 # Add the constant name to the 'consts' dictionary, with the
1241 # value being None to indicate that the constant isn't
1242 # an enumeration value.
1243 def genEnum(self, enuminfo, name):
1244 OutputGenerator.genEnum(self, enuminfo, name)
1245
1246 # @consts C enumerant/const name Name of corresponding 'enums' key
1247
1248 self.addName('consts', name, None)
1249 #
1250 # Command generation
1251 #
1252 # Add the command name to the 'protos' dictionary, with the
1253 # value being an ordered list of the parameter names.
1254 def genCmd(self, cmdinfo, name):
1255 OutputGenerator.genCmd(self, cmdinfo, name)
1256
1257 params = [param.text for param in cmdinfo.elem.findall('param/name')]
1258 self.addName('protos', name, params)
1259
1260# ValidityOutputGenerator - subclass of OutputGenerator.
1261# Generates AsciiDoc includes of valid usage information, for reference
1262# pages and the Vulkan specification. Similar to DocOutputGenerator.
1263#
1264# ---- methods ----
1265# ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for
1266# OutputGenerator. Defines additional internal state.
1267# ---- methods overriding base class ----
1268# beginFile(genOpts)
1269# endFile()
1270# beginFeature(interface, emit)
1271# endFeature()
1272# genCmd(cmdinfo)
1273class ValidityOutputGenerator(OutputGenerator):
1274 """Generate specified API interfaces in a specific style, such as a C header"""
1275 def __init__(self,
1276 errFile = sys.stderr,
1277 warnFile = sys.stderr,
1278 diagFile = sys.stdout):
1279 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
1280
1281 def beginFile(self, genOpts):
1282 OutputGenerator.beginFile(self, genOpts)
1283 def endFile(self):
1284 OutputGenerator.endFile(self)
1285 def beginFeature(self, interface, emit):
1286 # Start processing in superclass
1287 OutputGenerator.beginFeature(self, interface, emit)
1288 def endFeature(self):
1289 # Finish processing in superclass
1290 OutputGenerator.endFeature(self)
1291
1292 def makeParameterName(self, name):
1293 return 'pname:' + name
1294
1295 def makeStructName(self, name):
1296 return 'sname:' + name
1297
1298 def makeBaseTypeName(self, name):
1299 return 'basetype:' + name
1300
1301 def makeEnumerationName(self, name):
1302 return 'elink:' + name
1303
1304 def makeEnumerantName(self, name):
1305 return 'ename:' + name
1306
1307 def makeFLink(self, name):
1308 return 'flink:' + name
1309
1310 #
1311 # Generate an include file
1312 #
1313 # directory - subdirectory to put file in
1314 # basename - base name of the file
1315 # contents - contents of the file (Asciidoc boilerplate aside)
1316 def writeInclude(self, directory, basename, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes):
1317 # Create file
1318 filename = self.genOpts.genDirectory + '/' + directory + '/' + basename + '.txt'
1319 self.logMsg('diag', '# Generating include file:', filename)
1320 fp = open(filename, 'w')
1321 # Asciidoc anchor
1322
1323 # Valid Usage
1324 if validity is not None:
1325 write('.Valid Usage', file=fp)
1326 write('*' * 80, file=fp)
1327 write(validity, file=fp, end='')
1328 write('*' * 80, file=fp)
1329 write('', file=fp)
1330
1331 # Host Synchronization
1332 if threadsafety is not None:
1333 write('.Host Synchronization', file=fp)
1334 write('*' * 80, file=fp)
1335 write(threadsafety, file=fp, end='')
1336 write('*' * 80, file=fp)
1337 write('', file=fp)
Dustin Gravesf69e3772016-02-11 10:10:14 -07001338
Mike Stroyana451fa82016-01-07 15:35:37 -07001339 # Command Properties - contained within a block, to avoid table numbering
1340 if commandpropertiesentry is not None:
1341 write('.Command Properties', file=fp)
1342 write('*' * 80, file=fp)
1343 write('[options="header", width="100%"]', file=fp)
1344 write('|=====================', file=fp)
1345 write('|Command Buffer Levels|Render Pass Scope|Supported Queue Types', file=fp)
1346 write(commandpropertiesentry, file=fp)
1347 write('|=====================', file=fp)
1348 write('*' * 80, file=fp)
1349 write('', file=fp)
1350
1351 # Success Codes - contained within a block, to avoid table numbering
1352 if successcodes is not None or errorcodes is not None:
1353 write('.Return Codes', file=fp)
1354 write('*' * 80, file=fp)
1355 if successcodes is not None:
1356 write('<<fundamentals-successcodes,Success>>::', file=fp)
1357 write(successcodes, file=fp)
1358 if errorcodes is not None:
1359 write('<<fundamentals-errorcodes,Failure>>::', file=fp)
1360 write(errorcodes, file=fp)
1361 write('*' * 80, file=fp)
1362 write('', file=fp)
Dustin Gravesf69e3772016-02-11 10:10:14 -07001363
Mike Stroyana451fa82016-01-07 15:35:37 -07001364 fp.close()
1365
1366 #
1367 # Check if the parameter passed in is a pointer
1368 def paramIsPointer(self, param):
1369 ispointer = False
1370 paramtype = param.find('type')
1371 if paramtype.tail is not None and '*' in paramtype.tail:
1372 ispointer = True
1373
1374 return ispointer
1375
1376 #
1377 # Check if the parameter passed in is a static array
1378 def paramIsStaticArray(self, param):
1379 if param.find('name').tail is not None:
1380 if param.find('name').tail[0] == '[':
1381 return True
1382
1383 #
1384 # Get the length of a parameter that's been identified as a static array
1385 def staticArrayLength(self, param):
1386 paramname = param.find('name')
1387 paramenumsize = param.find('enum')
1388
1389 if paramenumsize is not None:
1390 return paramenumsize.text
1391 else:
1392 return paramname.tail[1:-1]
1393
1394 #
1395 # Check if the parameter passed in is a pointer to an array
1396 def paramIsArray(self, param):
1397 return param.attrib.get('len') is not None
1398
1399 #
1400 # Get the parent of a handle object
1401 def getHandleParent(self, typename):
1402 types = self.registry.findall("types/type")
1403 for elem in types:
1404 if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename:
1405 return elem.attrib.get('parent')
1406
1407 #
1408 # Check if a parent object is dispatchable or not
1409 def isHandleTypeDispatchable(self, handlename):
1410 handle = self.registry.find("types/type/[name='" + handlename + "'][@category='handle']")
1411 if handle is not None and handle.find('type').text == 'VK_DEFINE_HANDLE':
1412 return True
1413 else:
1414 return False
1415
1416 def isHandleOptional(self, param, params):
1417
1418 # See if the handle is optional
1419 isOptional = False
1420
1421 # Simple, if it's optional, return true
1422 if param.attrib.get('optional') is not None:
1423 return True
1424
1425 # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes.
1426 if param.attrib.get('noautovalidity') is not None:
1427 return True
1428
1429 # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional
1430 if self.paramIsArray(param):
1431 lengths = param.attrib.get('len').split(',')
1432 for length in lengths:
1433 if (length) != 'null-terminated' and (length) != '1':
1434 for otherparam in params:
1435 if otherparam.find('name').text == length:
1436 if otherparam.attrib.get('optional') is not None:
1437 return True
1438
1439 return False
1440 #
1441 # Get the category of a type
1442 def getTypeCategory(self, typename):
1443 types = self.registry.findall("types/type")
1444 for elem in types:
1445 if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename:
1446 return elem.attrib.get('category')
1447
1448 #
1449 # Make a chunk of text for the end of a parameter if it is an array
1450 def makeAsciiDocPreChunk(self, param, params):
1451 paramname = param.find('name')
1452 paramtype = param.find('type')
1453
1454 # General pre-amble. Check optionality and add stuff.
1455 asciidoc = '* '
1456
1457 if self.paramIsStaticArray(param):
1458 asciidoc += 'Any given element of '
1459
1460 elif self.paramIsArray(param):
1461 lengths = param.attrib.get('len').split(',')
1462
1463 # Find all the parameters that are called out as optional, so we can document that they might be zero, and the array may be ignored
1464 optionallengths = []
1465 for length in lengths:
1466 if (length) != 'null-terminated' and (length) != '1':
1467 for otherparam in params:
1468 if otherparam.find('name').text == length:
1469 if otherparam.attrib.get('optional') is not None:
1470 if self.paramIsPointer(otherparam):
1471 optionallengths.append('the value referenced by ' + self.makeParameterName(length))
1472 else:
1473 optionallengths.append(self.makeParameterName(length))
1474
1475 # Document that these arrays may be ignored if any of the length values are 0
1476 if len(optionallengths) != 0 or param.attrib.get('optional') is not None:
1477 asciidoc += 'If '
1478
1479
1480 if len(optionallengths) != 0:
1481 if len(optionallengths) == 1:
1482
1483 asciidoc += optionallengths[0]
1484 asciidoc += ' is '
1485
1486 else:
1487 asciidoc += ' or '.join(optionallengths)
1488 asciidoc += ' are '
1489
1490 asciidoc += 'not `0`, '
1491
1492 if len(optionallengths) != 0 and param.attrib.get('optional') is not None:
1493 asciidoc += 'and '
1494
1495 if param.attrib.get('optional') is not None:
1496 asciidoc += self.makeParameterName(paramname.text)
1497 asciidoc += ' is not `NULL`, '
1498
1499 elif param.attrib.get('optional') is not None:
1500 # Don't generate this stub for bitflags
1501 if self.getTypeCategory(paramtype.text) != 'bitmask':
1502 if param.attrib.get('optional').split(',')[0] == 'true':
1503 asciidoc += 'If '
1504 asciidoc += self.makeParameterName(paramname.text)
1505 asciidoc += ' is not '
1506 if self.paramIsArray(param) or self.paramIsPointer(param) or self.isHandleTypeDispatchable(paramtype.text):
1507 asciidoc += '`NULL`'
1508 elif self.getTypeCategory(paramtype.text) == 'handle':
1509 asciidoc += 'sname:VK_NULL_HANDLE'
1510 else:
1511 asciidoc += '`0`'
1512
1513 asciidoc += ', '
1514
1515 return asciidoc
1516
1517 #
1518 # Make the generic asciidoc line chunk portion used for all parameters.
1519 # May return an empty string if nothing to validate.
1520 def createValidationLineForParameterIntroChunk(self, param, params, typetext):
1521 asciidoc = ''
1522 paramname = param.find('name')
1523 paramtype = param.find('type')
1524
1525 asciidoc += self.makeAsciiDocPreChunk(param, params)
1526
1527 asciidoc += self.makeParameterName(paramname.text)
1528 asciidoc += ' must: be '
1529
1530 if self.paramIsArray(param):
1531 # Arrays. These are hard to get right, apparently
1532
1533 lengths = param.attrib.get('len').split(',')
1534
1535 if (lengths[0]) == 'null-terminated':
1536 asciidoc += 'a null-terminated '
1537 elif (lengths[0]) == '1':
1538 asciidoc += 'a pointer to '
1539 else:
1540 asciidoc += 'a pointer to an array of '
1541
1542 # Handle equations, which are currently denoted with latex
1543 if 'latexmath:' in lengths[0]:
1544 asciidoc += lengths[0]
1545 else:
1546 asciidoc += self.makeParameterName(lengths[0])
1547 asciidoc += ' '
1548
1549 for length in lengths[1:]:
1550 if (length) == 'null-terminated': # This should always be the last thing. If it ever isn't for some bizarre reason, then this will need some massaging.
1551 asciidoc += 'null-terminated '
1552 elif (length) == '1':
1553 asciidoc += 'pointers to '
1554 else:
1555 asciidoc += 'pointers to arrays of '
1556 # Handle equations, which are currently denoted with latex
1557 if 'latex:' in length:
1558 asciidoc += length
1559 else:
1560 asciidoc += self.makeParameterName(length)
1561 asciidoc += ' '
1562
1563 # Void pointers don't actually point at anything - remove the word "to"
1564 if paramtype.text == 'void':
1565 if lengths[-1] == '1':
1566 if len(lengths) > 1:
1567 asciidoc = asciidoc[:-5] # Take care of the extra s added by the post array chunk function. #HACK#
1568 else:
1569 asciidoc = asciidoc[:-4]
1570 else:
1571 # An array of void values is a byte array.
1572 asciidoc += 'byte'
1573
1574 elif paramtype.text == 'char':
1575 # A null terminated array of chars is a string
1576 if lengths[-1] == 'null-terminated':
1577 asciidoc += 'string'
1578 else:
1579 # Else it's just a bunch of chars
1580 asciidoc += 'char value'
1581 elif param.text is not None:
1582 # If a value is "const" that means it won't get modified, so it must be valid going into the function.
1583 if 'const' in param.text:
1584 typecategory = self.getTypeCategory(paramtype.text)
1585 if (typecategory != 'struct' and typecategory != 'union' and typecategory != 'basetype' and typecategory is not None) or not self.isStructAlwaysValid(paramtype.text):
1586 asciidoc += 'valid '
1587
1588 asciidoc += typetext
1589
1590 # pluralize
1591 if len(lengths) > 1 or (lengths[0] != '1' and lengths[0] != 'null-terminated'):
1592 asciidoc += 's'
1593
1594 elif self.paramIsPointer(param):
1595 # Handle pointers - which are really special case arrays (i.e. they don't have a length)
1596 pointercount = paramtype.tail.count('*')
1597
1598 # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that.
1599 for i in range(0, pointercount):
1600 asciidoc += 'a pointer to '
1601
1602 if paramtype.text == 'void':
1603 # If there's only one pointer, it's optional, and it doesn't point at anything in particular - we don't need any language.
1604 if pointercount == 1 and param.attrib.get('optional') is not None:
1605 return '' # early return
1606 else:
1607 # Pointer to nothing in particular - delete the " to " portion
1608 asciidoc = asciidoc[:-4]
1609 else:
1610 # Add an article for English semantic win
1611 asciidoc += 'a '
1612
1613 # If a value is "const" that means it won't get modified, so it must be valid going into the function.
1614 if param.text is not None and paramtype.text != 'void':
1615 if 'const' in param.text:
1616 asciidoc += 'valid '
1617
1618 asciidoc += typetext
1619
1620 else:
1621 # Non-pointer, non-optional things must be valid
1622 asciidoc += 'a valid '
1623 asciidoc += typetext
1624
1625 if asciidoc != '':
1626 asciidoc += '\n'
1627
1628 # Add additional line for non-optional bitmasks
1629 if self.getTypeCategory(paramtype.text) == 'bitmask':
1630 if param.attrib.get('optional') is None:
1631 asciidoc += '* '
1632 if self.paramIsArray(param):
1633 asciidoc += 'Each element of '
1634 asciidoc += 'pname:'
1635 asciidoc += paramname.text
1636 asciidoc += ' mustnot: be `0`'
1637 asciidoc += '\n'
1638
1639 return asciidoc
1640
1641 def makeAsciiDocLineForParameter(self, param, params, typetext):
1642 if param.attrib.get('noautovalidity') is not None:
1643 return ''
1644 asciidoc = self.createValidationLineForParameterIntroChunk(param, params, typetext)
1645
1646 return asciidoc
1647
1648 # Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance)
1649 def isStructAlwaysValid(self, structname):
1650
1651 struct = self.registry.find("types/type[@name='" + structname + "']")
1652
1653 params = struct.findall('member')
1654 validity = struct.find('validity')
1655
1656 if validity is not None:
1657 return False
1658
1659 for param in params:
1660 paramname = param.find('name')
1661 paramtype = param.find('type')
1662 typecategory = self.getTypeCategory(paramtype.text)
1663
1664 if paramname.text == 'pNext':
1665 return False
1666
1667 if paramname.text == 'sType':
1668 return False
1669
1670 if paramtype.text == 'void' or paramtype.text == 'char' or self.paramIsArray(param) or self.paramIsPointer(param):
1671 if self.makeAsciiDocLineForParameter(param, params, '') != '':
1672 return False
1673 elif typecategory == 'handle' or typecategory == 'enum' or typecategory == 'bitmask' or param.attrib.get('returnedonly') == 'true':
1674 return False
1675 elif typecategory == 'struct' or typecategory == 'union':
1676 if self.isStructAlwaysValid(paramtype.text) is False:
1677 return False
1678
1679 return True
1680
1681 #
1682 # Make an entire asciidoc line for a given parameter
1683 def createValidationLineForParameter(self, param, params, typecategory):
1684 asciidoc = ''
1685 paramname = param.find('name')
1686 paramtype = param.find('type')
1687
1688 if paramtype.text == 'void' or paramtype.text == 'char':
1689 # Chars and void are special cases - needs care inside the generator functions
1690 # A null-terminated char array is a string, else it's chars.
1691 # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular
1692 asciidoc += self.makeAsciiDocLineForParameter(param, params, '')
1693 elif typecategory == 'bitmask':
1694 bitsname = paramtype.text.replace('Flags', 'FlagBits')
1695 if self.registry.find("enums[@name='" + bitsname + "']") is None:
1696 asciidoc += '* '
1697 asciidoc += self.makeParameterName(paramname.text)
1698 asciidoc += ' must: be `0`'
1699 asciidoc += '\n'
1700 else:
1701 if self.paramIsArray(param):
1702 asciidoc += self.makeAsciiDocLineForParameter(param, params, 'combinations of ' + self.makeEnumerationName(bitsname) + ' value')
1703 else:
1704 asciidoc += self.makeAsciiDocLineForParameter(param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values')
1705 elif typecategory == 'handle':
1706 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' handle')
1707 elif typecategory == 'enum':
1708 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeEnumerationName(paramtype.text) + ' value')
1709 elif typecategory == 'struct':
1710 if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(paramtype.text):
1711 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' structure')
1712 elif typecategory == 'union':
1713 if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(paramtype.text):
1714 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' union')
1715 elif self.paramIsArray(param) or self.paramIsPointer(param):
1716 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeBaseTypeName(paramtype.text) + ' value')
1717
1718 return asciidoc
1719
1720 #
1721 # Make an asciidoc validity entry for a handle's parent object
1722 def makeAsciiDocHandleParent(self, param, params):
1723 asciidoc = ''
1724 paramname = param.find('name')
1725 paramtype = param.find('type')
1726
1727 # Deal with handle parents
1728 handleparent = self.getHandleParent(paramtype.text)
1729 if handleparent is not None:
1730 parentreference = None
1731 for otherparam in params:
1732 if otherparam.find('type').text == handleparent:
1733 parentreference = otherparam.find('name').text
1734 if parentreference is not None:
1735 asciidoc += '* '
1736
1737 if self.isHandleOptional(param, params):
1738 if self.paramIsArray(param):
1739 asciidoc += 'Each element of '
1740 asciidoc += self.makeParameterName(paramname.text)
1741 asciidoc += ' that is a valid handle'
1742 else:
1743 asciidoc += 'If '
1744 asciidoc += self.makeParameterName(paramname.text)
1745 asciidoc += ' is a valid handle, it'
1746 else:
1747 if self.paramIsArray(param):
1748 asciidoc += 'Each element of '
1749 asciidoc += self.makeParameterName(paramname.text)
1750 asciidoc += ' must: have been created, allocated or retrieved from '
1751 asciidoc += self.makeParameterName(parentreference)
1752
1753 asciidoc += '\n'
1754 return asciidoc
1755
1756 #
1757 # Generate an asciidoc validity line for the sType value of a struct
1758 def makeStructureType(self, blockname, param):
1759 asciidoc = '* '
1760 paramname = param.find('name')
1761 paramtype = param.find('type')
1762
1763 asciidoc += self.makeParameterName(paramname.text)
1764 asciidoc += ' must: be '
1765
1766 structuretype = ''
1767 for elem in re.findall(r'(([A-Z][a-z]+)|([A-Z][A-Z]+))', blockname):
1768 if elem[0] == 'Vk':
1769 structuretype += 'VK_STRUCTURE_TYPE_'
1770 else:
1771 structuretype += elem[0].upper()
1772 structuretype += '_'
1773
1774 asciidoc += self.makeEnumerantName(structuretype[:-1])
1775 asciidoc += '\n'
1776
1777 return asciidoc
1778
1779 #
1780 # Generate an asciidoc validity line for the pNext value of a struct
1781 def makeStructureExtensionPointer(self, param):
1782 asciidoc = '* '
1783 paramname = param.find('name')
1784 paramtype = param.find('type')
1785
1786 asciidoc += self.makeParameterName(paramname.text)
1787
1788 validextensionstructs = param.attrib.get('validextensionstructs')
1789 if validextensionstructs is None:
1790 asciidoc += ' must: be `NULL`'
1791 else:
1792 extensionstructs = validextensionstructs.split(',')
1793 asciidoc += ' must: point to one of ' + extensionstructs[:-1].join(', ') + ' or ' + extensionstructs[-1] + 'if the extension that introduced them is enabled '
1794
1795 asciidoc += '\n'
1796
1797 return asciidoc
1798
1799 #
1800 # Generate all the valid usage information for a given struct or command
1801 def makeValidUsageStatements(self, cmd, blockname, params, usages):
1802 # Start the asciidoc block for this
1803 asciidoc = ''
1804
1805 handles = []
1806 anyparentedhandlesoptional = False
1807 parentdictionary = {}
1808 arraylengths = set()
1809 for param in params:
1810 paramname = param.find('name')
1811 paramtype = param.find('type')
1812
1813 # Get the type's category
1814 typecategory = self.getTypeCategory(paramtype.text)
1815
1816 # Generate language to independently validate a parameter
1817 if paramtype.text == 'VkStructureType' and paramname.text == 'sType':
1818 asciidoc += self.makeStructureType(blockname, param)
1819 elif paramtype.text == 'void' and paramname.text == 'pNext':
1820 asciidoc += self.makeStructureExtensionPointer(param)
1821 else:
1822 asciidoc += self.createValidationLineForParameter(param, params, typecategory)
1823
1824 # Ensure that any parenting is properly validated, and list that a handle was found
1825 if typecategory == 'handle':
1826 # Don't detect a parent for return values!
1827 if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text):
1828 parent = self.getHandleParent(paramtype.text)
1829 if parent is not None:
1830 handles.append(param)
1831
1832 # If any param is optional, it affects the output
1833 if self.isHandleOptional(param, params):
1834 anyparentedhandlesoptional = True
1835
1836 # Find the first dispatchable parent
1837 ancestor = parent
1838 while ancestor is not None and not self.isHandleTypeDispatchable(ancestor):
1839 ancestor = self.getHandleParent(ancestor)
1840
1841 # If one was found, add this parameter to the parent dictionary
1842 if ancestor is not None:
1843 if ancestor not in parentdictionary:
1844 parentdictionary[ancestor] = []
1845
1846 if self.paramIsArray(param):
1847 parentdictionary[ancestor].append('the elements of ' + self.makeParameterName(paramname.text))
1848 else:
1849 parentdictionary[ancestor].append(self.makeParameterName(paramname.text))
1850
1851 # Get the array length for this parameter
1852 arraylength = param.attrib.get('len')
1853 if arraylength is not None:
1854 for onelength in arraylength.split(','):
1855 arraylengths.add(onelength)
1856
1857 # For any vkQueue* functions, there might be queue type data
1858 if 'vkQueue' in blockname:
1859 # The queue type must be valid
1860 queuetypes = cmd.attrib.get('queues')
1861 if queuetypes is not None:
1862 queuebits = []
1863 for queuetype in re.findall(r'([^,]+)', queuetypes):
1864 queuebits.append(queuetype.replace('_',' '))
1865
1866 asciidoc += '* '
1867 asciidoc += 'The pname:queue must: support '
1868 if len(queuebits) == 1:
1869 asciidoc += queuebits[0]
1870 else:
1871 asciidoc += (', ').join(queuebits[:-1])
1872 asciidoc += ' or '
1873 asciidoc += queuebits[-1]
1874 asciidoc += ' operations'
1875 asciidoc += '\n'
1876
1877 if 'vkCmd' in blockname:
1878 # The commandBuffer parameter must be being recorded
1879 asciidoc += '* '
1880 asciidoc += 'pname:commandBuffer must: be in the recording state'
1881 asciidoc += '\n'
1882
1883 # The queue type must be valid
1884 queuetypes = cmd.attrib.get('queues')
1885 queuebits = []
1886 for queuetype in re.findall(r'([^,]+)', queuetypes):
1887 queuebits.append(queuetype.replace('_',' '))
1888
1889 asciidoc += '* '
1890 asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1891 if len(queuebits) == 1:
1892 asciidoc += queuebits[0]
1893 else:
1894 asciidoc += (', ').join(queuebits[:-1])
1895 asciidoc += ' or '
1896 asciidoc += queuebits[-1]
1897 asciidoc += ' operations'
1898 asciidoc += '\n'
1899
1900 # Must be called inside/outside a renderpass appropriately
1901 renderpass = cmd.attrib.get('renderpass')
1902
1903 if renderpass != 'both':
1904 asciidoc += '* This command must: only be called '
1905 asciidoc += renderpass
1906 asciidoc += ' of a render pass instance'
1907 asciidoc += '\n'
1908
1909 # Must be in the right level command buffer
1910 cmdbufferlevel = cmd.attrib.get('cmdbufferlevel')
1911
1912 if cmdbufferlevel != 'primary,secondary':
1913 asciidoc += '* pname:commandBuffer must: be a '
1914 asciidoc += cmdbufferlevel
1915 asciidoc += ' sname:VkCommandBuffer'
1916 asciidoc += '\n'
1917
1918 # Any non-optional arraylengths should specify they must be greater than 0
1919 for param in params:
1920 paramname = param.find('name')
1921
1922 for arraylength in arraylengths:
1923 if paramname.text == arraylength and param.attrib.get('optional') is None:
1924 # Get all the array dependencies
1925 arrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
1926
1927 # Get all the optional array dependencies, including those not generating validity for some reason
1928 optionalarrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
1929 optionalarrays.extend(cmd.findall("param/[@len='" + arraylength + "'][@noautovalidity='true']"))
1930
1931 asciidoc += '* '
1932
1933 # Allow lengths to be arbitrary if all their dependents are optional
1934 if len(optionalarrays) == len(arrays) and len(optionalarrays) != 0:
1935 asciidoc += 'If '
1936 if len(optionalarrays) > 1:
1937 asciidoc += 'any of '
1938
1939 for array in optionalarrays[:-1]:
1940 asciidoc += self.makeParameterName(optionalarrays.find('name').text)
1941 asciidoc += ', '
1942
1943 if len(optionalarrays) > 1:
1944 asciidoc += 'and '
1945 asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
1946 asciidoc += ' are '
1947 else:
1948 asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
1949 asciidoc += ' is '
1950
1951 asciidoc += 'not `NULL`, '
1952
1953 if self.paramIsPointer(param):
1954 asciidoc += 'the value referenced by '
1955 else:
1956 asciidoc += 'the value of '
1957
1958 elif self.paramIsPointer(param):
1959 asciidoc += 'The value referenced by '
1960 else:
1961 asciidoc += 'The value of '
1962
1963 asciidoc += self.makeParameterName(arraylength)
1964 asciidoc += ' must: be greater than `0`'
1965 asciidoc += '\n'
1966
1967 # Find the parents of all objects referenced in this command
1968 for param in handles:
1969 asciidoc += self.makeAsciiDocHandleParent(param, params)
1970
1971 # Find the common ancestors of objects
1972 noancestorscount = 0
1973 while noancestorscount < len(parentdictionary):
1974 noancestorscount = 0
1975 oldparentdictionary = parentdictionary.copy()
1976 for parent in oldparentdictionary.items():
1977 ancestor = self.getHandleParent(parent[0])
1978
1979 while ancestor is not None and ancestor not in parentdictionary:
1980 ancestor = self.getHandleParent(ancestor)
1981
1982 if ancestor is not None:
1983 parentdictionary[ancestor] += parentdictionary.pop(parent[0])
1984 else:
1985 # No ancestors possible - so count it up
1986 noancestorscount += 1
1987
1988 # Add validation language about common ancestors
1989 for parent in parentdictionary.items():
1990 if len(parent[1]) > 1:
1991 parentlanguage = '* '
1992
1993 parentlanguage += 'Each of '
1994 parentlanguage += ", ".join(parent[1][:-1])
1995 parentlanguage += ' and '
1996 parentlanguage += parent[1][-1]
1997 if anyparentedhandlesoptional is True:
1998 parentlanguage += ' that are valid handles'
1999 parentlanguage += ' must: have been created, allocated or retrieved from the same '
2000 parentlanguage += self.makeStructName(parent[0])
2001 parentlanguage += '\n'
2002
2003 # Capitalize and add to the main language
2004 asciidoc += parentlanguage
2005
2006 # Add in any plain-text validation language that's in the xml
2007 for usage in usages:
2008 asciidoc += '* '
2009 asciidoc += usage.text
2010 asciidoc += '\n'
2011
2012 # In case there's nothing to report, return None
2013 if asciidoc == '':
2014 return None
2015 # Delimit the asciidoc block
2016 return asciidoc
2017
2018 def makeThreadSafetyBlock(self, cmd, paramtext):
2019 """Generate C function pointer typedef for <command> Element"""
2020 paramdecl = ''
2021
2022 # For any vkCmd* functions, the commandBuffer parameter must be being recorded
2023 if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name'):
2024 paramdecl += '* '
2025 paramdecl += 'The sname:VkCommandPool that pname:commandBuffer was created from'
2026 paramdecl += '\n'
2027
2028 # Find and add any parameters that are thread unsafe
2029 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
2030 if (explicitexternsyncparams is not None):
2031 for param in explicitexternsyncparams:
2032 externsyncattribs = param.attrib.get('externsync')
2033 paramname = param.find('name')
2034 for externsyncattrib in externsyncattribs.split(','):
2035 paramdecl += '* '
2036 paramdecl += 'Host access to '
2037 if externsyncattrib == 'true':
2038 if self.paramIsArray(param):
2039 paramdecl += 'each member of ' + self.makeParameterName(paramname.text)
2040 elif self.paramIsPointer(param):
2041 paramdecl += 'the object referenced by ' + self.makeParameterName(paramname.text)
2042 else:
2043 paramdecl += self.makeParameterName(paramname.text)
2044 else:
2045 paramdecl += 'pname:'
2046 paramdecl += externsyncattrib
2047 paramdecl += ' must: be externally synchronized\n'
2048
2049 # Find and add any "implicit" parameters that are thread unsafe
2050 implicitexternsyncparams = cmd.find('implicitexternsyncparams')
2051 if (implicitexternsyncparams is not None):
2052 for elem in implicitexternsyncparams:
2053 paramdecl += '* '
2054 paramdecl += 'Host access to '
2055 paramdecl += elem.text
2056 paramdecl += ' must: be externally synchronized\n'
2057
2058 if (paramdecl == ''):
2059 return None
2060 else:
2061 return paramdecl
2062
2063 def makeCommandPropertiesTableEntry(self, cmd, name):
Dustin Gravesf69e3772016-02-11 10:10:14 -07002064
Mike Stroyana451fa82016-01-07 15:35:37 -07002065 if 'vkCmd' in name:
2066 # Must be called inside/outside a renderpass appropriately
2067 cmdbufferlevel = cmd.attrib.get('cmdbufferlevel')
2068 cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(','))
Dustin Gravesf69e3772016-02-11 10:10:14 -07002069
Mike Stroyana451fa82016-01-07 15:35:37 -07002070 renderpass = cmd.attrib.get('renderpass')
2071 renderpass = renderpass.capitalize()
Dustin Gravesf69e3772016-02-11 10:10:14 -07002072
Mike Stroyana451fa82016-01-07 15:35:37 -07002073 queues = cmd.attrib.get('queues')
2074 queues = (' + \n').join(queues.upper().split(','))
Dustin Gravesf69e3772016-02-11 10:10:14 -07002075
2076 return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues
Mike Stroyana451fa82016-01-07 15:35:37 -07002077 elif 'vkQueue' in name:
2078 # Must be called inside/outside a renderpass appropriately
Dustin Gravesf69e3772016-02-11 10:10:14 -07002079
Mike Stroyana451fa82016-01-07 15:35:37 -07002080 queues = cmd.attrib.get('queues')
2081 if queues is None:
2082 queues = 'Any'
2083 else:
2084 queues = (' + \n').join(queues.upper().split(','))
Dustin Gravesf69e3772016-02-11 10:10:14 -07002085
2086 return '|-|-|' + queues
Mike Stroyana451fa82016-01-07 15:35:37 -07002087
2088 return None
Dustin Gravesf69e3772016-02-11 10:10:14 -07002089
Mike Stroyana451fa82016-01-07 15:35:37 -07002090 def makeSuccessCodes(self, cmd, name):
2091
2092 successcodes = cmd.attrib.get('successcodes')
2093 if successcodes is not None:
Dustin Gravesf69e3772016-02-11 10:10:14 -07002094
Mike Stroyana451fa82016-01-07 15:35:37 -07002095 successcodeentry = ''
2096 successcodes = successcodes.split(',')
2097 return '* ' + '\n* '.join(successcodes)
2098
2099 return None
2100
2101 def makeErrorCodes(self, cmd, name):
2102
2103 errorcodes = cmd.attrib.get('errorcodes')
2104 if errorcodes is not None:
Dustin Gravesf69e3772016-02-11 10:10:14 -07002105
Mike Stroyana451fa82016-01-07 15:35:37 -07002106 errorcodeentry = ''
2107 errorcodes = errorcodes.split(',')
2108 return '* ' + '\n* '.join(errorcodes)
2109
2110 return None
2111
2112 #
2113 # Command generation
2114 def genCmd(self, cmdinfo, name):
2115 OutputGenerator.genCmd(self, cmdinfo, name)
2116 #
2117 # Get all thh parameters
2118 params = cmdinfo.elem.findall('param')
2119 usages = cmdinfo.elem.findall('validity/usage')
2120
2121 validity = self.makeValidUsageStatements(cmdinfo.elem, name, params, usages)
2122 threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param')
2123 commandpropertiesentry = self.makeCommandPropertiesTableEntry(cmdinfo.elem, name)
2124 successcodes = self.makeSuccessCodes(cmdinfo.elem, name)
2125 errorcodes = self.makeErrorCodes(cmdinfo.elem, name)
2126
2127 self.writeInclude('validity/protos', name, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes)
2128
2129 #
2130 # Struct Generation
2131 def genStruct(self, typeinfo, typename):
2132 OutputGenerator.genStruct(self, typeinfo, typename)
2133
2134 # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information.
2135 if typeinfo.elem.attrib.get('returnedonly') is None:
2136 params = typeinfo.elem.findall('member')
2137 usages = typeinfo.elem.findall('validity/usage')
2138
2139 validity = self.makeValidUsageStatements(typeinfo.elem, typename, params, usages)
2140 threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member')
2141
2142 self.writeInclude('validity/structs', typename, validity, threadsafety, None, None, None)
2143 else:
2144 # Still generate files for return only structs, in case this state changes later
2145 self.writeInclude('validity/structs', typename, None, None, None, None, None)
2146
2147 #
2148 # Type Generation
2149 def genType(self, typeinfo, typename):
2150 OutputGenerator.genType(self, typeinfo, typename)
2151
2152 category = typeinfo.elem.get('category')
2153 if (category == 'struct' or category == 'union'):
2154 self.genStruct(typeinfo, typename)
2155
2156# HostSynchronizationOutputGenerator - subclass of OutputGenerator.
2157# Generates AsciiDoc includes of the externsync parameter table for the
2158# fundamentals chapter of the Vulkan specification. Similar to
2159# DocOutputGenerator.
2160#
2161# ---- methods ----
2162# HostSynchronizationOutputGenerator(errFile, warnFile, diagFile) - args as for
2163# OutputGenerator. Defines additional internal state.
2164# ---- methods overriding base class ----
2165# genCmd(cmdinfo)
2166class HostSynchronizationOutputGenerator(OutputGenerator):
2167 # Generate Host Synchronized Parameters in a table at the top of the spec
2168 def __init__(self,
2169 errFile = sys.stderr,
2170 warnFile = sys.stderr,
2171 diagFile = sys.stdout):
2172 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
2173
2174 threadsafety = {'parameters': '', 'parameterlists': '', 'implicit': ''}
2175
2176 def makeParameterName(self, name):
2177 return 'pname:' + name
2178
2179 def makeFLink(self, name):
2180 return 'flink:' + name
2181
2182 #
2183 # Generate an include file
2184 #
2185 # directory - subdirectory to put file in
2186 # basename - base name of the file
2187 # contents - contents of the file (Asciidoc boilerplate aside)
2188 def writeInclude(self):
2189
2190 if self.threadsafety['parameters'] is not None:
2191 # Create file
2192 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/parameters.txt'
2193 self.logMsg('diag', '# Generating include file:', filename)
2194 fp = open(filename, 'w')
2195
2196 # Host Synchronization
2197 write('.Externally Synchronized Parameters', file=fp)
2198 write('*' * 80, file=fp)
2199 write(self.threadsafety['parameters'], file=fp, end='')
2200 write('*' * 80, file=fp)
2201 write('', file=fp)
2202
2203 if self.threadsafety['parameterlists'] is not None:
2204 # Create file
2205 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/parameterlists.txt'
2206 self.logMsg('diag', '# Generating include file:', filename)
2207 fp = open(filename, 'w')
2208
2209 # Host Synchronization
2210 write('.Externally Synchronized Parameter Lists', file=fp)
2211 write('*' * 80, file=fp)
2212 write(self.threadsafety['parameterlists'], file=fp, end='')
2213 write('*' * 80, file=fp)
2214 write('', file=fp)
2215
2216 if self.threadsafety['implicit'] is not None:
2217 # Create file
2218 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/implicit.txt'
2219 self.logMsg('diag', '# Generating include file:', filename)
2220 fp = open(filename, 'w')
2221
2222 # Host Synchronization
2223 write('.Implicit Externally Synchronized Parameters', file=fp)
2224 write('*' * 80, file=fp)
2225 write(self.threadsafety['implicit'], file=fp, end='')
2226 write('*' * 80, file=fp)
2227 write('', file=fp)
2228
2229 fp.close()
2230
2231 #
2232 # Check if the parameter passed in is a pointer to an array
2233 def paramIsArray(self, param):
2234 return param.attrib.get('len') is not None
2235
2236 # Check if the parameter passed in is a pointer
2237 def paramIsPointer(self, param):
2238 ispointer = False
2239 paramtype = param.find('type')
2240 if paramtype.tail is not None and '*' in paramtype.tail:
2241 ispointer = True
2242
2243 return ispointer
2244
2245 # Turn the "name[].member[]" notation into plain English.
2246 def makeThreadDereferenceHumanReadable(self, dereference):
2247 matches = re.findall(r"[\w]+[^\w]*",dereference)
2248 stringval = ''
2249 for match in reversed(matches):
2250 if '->' in match or '.' in match:
2251 stringval += 'member of '
2252 if '[]' in match:
2253 stringval += 'each element of '
2254
2255 stringval += 'the '
2256 stringval += self.makeParameterName(re.findall(r"[\w]+",match)[0])
2257 stringval += ' '
2258
2259 stringval += 'parameter'
2260
2261 return stringval[0].upper() + stringval[1:]
2262
2263 def makeThreadSafetyBlocks(self, cmd, paramtext):
2264 protoname = cmd.find('proto/name').text
2265
2266 # Find and add any parameters that are thread unsafe
2267 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
2268 if (explicitexternsyncparams is not None):
2269 for param in explicitexternsyncparams:
2270 externsyncattribs = param.attrib.get('externsync')
2271 paramname = param.find('name')
2272 for externsyncattrib in externsyncattribs.split(','):
2273
2274 tempstring = '* '
2275 if externsyncattrib == 'true':
2276 if self.paramIsArray(param):
2277 tempstring += 'Each element of the '
2278 elif self.paramIsPointer(param):
2279 tempstring += 'The object referenced by the '
2280 else:
2281 tempstring += 'The '
2282
2283 tempstring += self.makeParameterName(paramname.text)
2284 tempstring += ' parameter'
2285
2286 else:
2287 tempstring += self.makeThreadDereferenceHumanReadable(externsyncattrib)
2288
2289 tempstring += ' in '
2290 tempstring += self.makeFLink(protoname)
2291 tempstring += '\n'
2292
2293
2294 if ' element of ' in tempstring:
2295 self.threadsafety['parameterlists'] += tempstring
2296 else:
2297 self.threadsafety['parameters'] += tempstring
2298
2299
2300 # Find and add any "implicit" parameters that are thread unsafe
2301 implicitexternsyncparams = cmd.find('implicitexternsyncparams')
2302 if (implicitexternsyncparams is not None):
2303 for elem in implicitexternsyncparams:
2304 self.threadsafety['implicit'] += '* '
2305 self.threadsafety['implicit'] += elem.text[0].upper()
2306 self.threadsafety['implicit'] += elem.text[1:]
2307 self.threadsafety['implicit'] += ' in '
2308 self.threadsafety['implicit'] += self.makeFLink(protoname)
2309 self.threadsafety['implicit'] += '\n'
2310
2311
2312 # For any vkCmd* functions, the commandBuffer parameter must be being recorded
2313 if protoname is not None and 'vkCmd' in protoname:
2314 self.threadsafety['implicit'] += '* '
2315 self.threadsafety['implicit'] += 'The sname:VkCommandPool that pname:commandBuffer was allocated from, in '
2316 self.threadsafety['implicit'] += self.makeFLink(protoname)
2317
2318 self.threadsafety['implicit'] += '\n'
2319
2320 #
2321 # Command generation
2322 def genCmd(self, cmdinfo, name):
2323 OutputGenerator.genCmd(self, cmdinfo, name)
2324 #
2325 # Get all thh parameters
2326 params = cmdinfo.elem.findall('param')
2327 usages = cmdinfo.elem.findall('validity/usage')
2328
2329 self.makeThreadSafetyBlocks(cmdinfo.elem, 'param')
2330
2331 self.writeInclude()
Mike Stroyan8849f9a2015-11-02 15:30:20 -07002332
2333# ThreadOutputGenerator - subclass of OutputGenerator.
2334# Generates Thread checking framework
2335#
2336# ---- methods ----
2337# ThreadOutputGenerator(errFile, warnFile, diagFile) - args as for
2338# OutputGenerator. Defines additional internal state.
2339# ---- methods overriding base class ----
2340# beginFile(genOpts)
2341# endFile()
2342# beginFeature(interface, emit)
2343# endFeature()
2344# genType(typeinfo,name)
2345# genStruct(typeinfo,name)
2346# genGroup(groupinfo,name)
2347# genEnum(enuminfo, name)
2348# genCmd(cmdinfo)
2349class ThreadOutputGenerator(OutputGenerator):
2350 """Generate specified API interfaces in a specific style, such as a C header"""
2351 # This is an ordered list of sections in the header file.
2352 TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
2353 'group', 'bitmask', 'funcpointer', 'struct']
2354 ALL_SECTIONS = TYPE_SECTIONS + ['command']
2355 def __init__(self,
2356 errFile = sys.stderr,
2357 warnFile = sys.stderr,
2358 diagFile = sys.stdout):
2359 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
2360 # Internal state - accumulators for different inner block text
2361 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
2362 self.intercepts = []
2363
2364 # Check if the parameter passed in is a pointer to an array
2365 def paramIsArray(self, param):
2366 return param.attrib.get('len') is not None
2367
2368 # Check if the parameter passed in is a pointer
2369 def paramIsPointer(self, param):
2370 ispointer = False
2371 for elem in param:
2372 #write('paramIsPointer '+elem.text, file=sys.stderr)
2373 #write('elem.tag '+elem.tag, file=sys.stderr)
2374 #if (elem.tail is None):
2375 # write('elem.tail is None', file=sys.stderr)
2376 #else:
2377 # write('elem.tail '+elem.tail, file=sys.stderr)
2378 if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail:
2379 ispointer = True
2380 # write('is pointer', file=sys.stderr)
2381 return ispointer
2382 def makeThreadUseBlock(self, cmd, functionprefix):
2383 """Generate C function pointer typedef for <command> Element"""
2384 paramdecl = ''
2385 thread_check_dispatchable_objects = [
2386 "VkCommandBuffer",
2387 "VkDevice",
2388 "VkInstance",
2389 "VkQueue",
2390 ]
2391 thread_check_nondispatchable_objects = [
2392 "VkBuffer",
2393 "VkBufferView",
2394 "VkCommandPool",
2395 "VkDescriptorPool",
2396 "VkDescriptorSetLayout",
2397 "VkDeviceMemory",
2398 "VkEvent",
2399 "VkFence",
2400 "VkFramebuffer",
2401 "VkImage",
2402 "VkImageView",
2403 "VkPipeline",
2404 "VkPipelineCache",
2405 "VkPipelineLayout",
2406 "VkQueryPool",
2407 "VkRenderPass",
2408 "VkSampler",
2409 "VkSemaphore",
2410 "VkShaderModule",
2411 ]
2412
2413 # Find and add any parameters that are thread unsafe
2414 params = cmd.findall('param')
2415 for param in params:
2416 paramname = param.find('name')
2417 if False: # self.paramIsPointer(param):
2418 paramdecl += ' // not watching use of pointer ' + paramname.text + '\n'
2419 else:
2420 externsync = param.attrib.get('externsync')
2421 if externsync == 'true':
2422 if self.paramIsArray(param):
2423 paramdecl += ' for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n'
2424 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + '[index]);\n'
2425 paramdecl += ' }\n'
2426 else:
2427 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + ');\n'
2428 elif (param.attrib.get('externsync')):
2429 if self.paramIsArray(param):
2430 # Externsync can list pointers to arrays of members to synchronize
2431 paramdecl += ' for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n'
2432 for member in externsync.split(","):
2433 # Replace first empty [] in member name with index
2434 element = member.replace('[]','[index]',1)
2435 if '[]' in element:
2436 # Replace any second empty [] in element name with
2437 # inner array index based on mapping array names like
2438 # "pSomeThings[]" to "someThingCount" array size.
2439 # This could be more robust by mapping a param member
2440 # name to a struct type and "len" attribute.
2441 limit = element[0:element.find('s[]')] + 'Count'
2442 dotp = limit.rfind('.p')
2443 limit = limit[0:dotp+1] + limit[dotp+2:dotp+3].lower() + limit[dotp+3:]
2444 paramdecl += ' for(int index2=0;index2<'+limit+';index2++)'
2445 element = element.replace('[]','[index2]')
2446 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + element + ');\n'
2447 paramdecl += ' }\n'
2448 else:
2449 # externsync can list members to synchronize
2450 for member in externsync.split(","):
2451 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + member + ');\n'
2452 else:
2453 paramtype = param.find('type')
2454 if paramtype is not None:
2455 paramtype = paramtype.text
2456 else:
2457 paramtype = 'None'
2458 if paramtype in thread_check_dispatchable_objects or paramtype in thread_check_nondispatchable_objects:
2459 if self.paramIsArray(param) and ('pPipelines' != paramname.text):
2460 paramdecl += ' for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n'
2461 paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + '[index]);\n'
2462 paramdecl += ' }\n'
2463 elif not self.paramIsPointer(param):
2464 # Pointer params are often being created.
2465 # They are not being read from.
2466 paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + ');\n'
2467 explicitexternsyncparams = cmd.findall("param[@externsync]")
2468 if (explicitexternsyncparams is not None):
2469 for param in explicitexternsyncparams:
2470 externsyncattrib = param.attrib.get('externsync')
2471 paramname = param.find('name')
2472 paramdecl += '// Host access to '
2473 if externsyncattrib == 'true':
2474 if self.paramIsArray(param):
2475 paramdecl += 'each member of ' + paramname.text
2476 elif self.paramIsPointer(param):
2477 paramdecl += 'the object referenced by ' + paramname.text
2478 else:
2479 paramdecl += paramname.text
2480 else:
2481 paramdecl += externsyncattrib
2482 paramdecl += ' must be externally synchronized\n'
2483
2484 # Find and add any "implicit" parameters that are thread unsafe
2485 implicitexternsyncparams = cmd.find('implicitexternsyncparams')
2486 if (implicitexternsyncparams is not None):
2487 for elem in implicitexternsyncparams:
2488 paramdecl += ' // '
2489 paramdecl += elem.text
2490 paramdecl += ' must be externally synchronized between host accesses\n'
2491
2492 if (paramdecl == ''):
2493 return None
2494 else:
2495 return paramdecl
2496 def beginFile(self, genOpts):
2497 OutputGenerator.beginFile(self, genOpts)
2498 # C-specific
2499 #
2500 # Multiple inclusion protection & C++ wrappers.
2501 if (genOpts.protectFile and self.genOpts.filename):
2502 headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
2503 write('#ifndef', headerSym, file=self.outFile)
2504 write('#define', headerSym, '1', file=self.outFile)
2505 self.newline()
2506 write('#ifdef __cplusplus', file=self.outFile)
2507 write('extern "C" {', file=self.outFile)
2508 write('#endif', file=self.outFile)
2509 self.newline()
2510 #
2511 # User-supplied prefix text, if any (list of strings)
2512 if (genOpts.prefixText):
2513 for s in genOpts.prefixText:
2514 write(s, file=self.outFile)
2515 def endFile(self):
2516 # C-specific
2517 # Finish C++ wrapper and multiple inclusion protection
2518 self.newline()
2519 # record intercepted procedures
2520 write('// intercepts', file=self.outFile)
2521 write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile)
2522 write('\n'.join(self.intercepts), file=self.outFile)
2523 write('};\n', file=self.outFile)
2524 self.newline()
2525 write('#ifdef __cplusplus', file=self.outFile)
2526 write('}', file=self.outFile)
2527 write('#endif', file=self.outFile)
2528 if (self.genOpts.protectFile and self.genOpts.filename):
2529 self.newline()
2530 write('#endif', file=self.outFile)
2531 # Finish processing in superclass
2532 OutputGenerator.endFile(self)
2533 def beginFeature(self, interface, emit):
2534 #write('// starting beginFeature', file=self.outFile)
2535 # Start processing in superclass
2536 OutputGenerator.beginFeature(self, interface, emit)
2537 # C-specific
2538 # Accumulate includes, defines, types, enums, function pointer typedefs,
2539 # end function prototypes separately for this feature. They're only
2540 # printed in endFeature().
2541 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
2542 #write('// ending beginFeature', file=self.outFile)
2543 def endFeature(self):
2544 # C-specific
2545 # Actually write the interface to the output file.
2546 #write('// starting endFeature', file=self.outFile)
2547 if (self.emit):
2548 self.newline()
2549 if (self.genOpts.protectFeature):
2550 write('#ifndef', self.featureName, file=self.outFile)
2551 # If type declarations are needed by other features based on
2552 # this one, it may be necessary to suppress the ExtraProtect,
2553 # or move it below the 'for section...' loop.
2554 #write('// endFeature looking at self.featureExtraProtect', file=self.outFile)
2555 if (self.featureExtraProtect != None):
2556 write('#ifdef', self.featureExtraProtect, file=self.outFile)
2557 #write('#define', self.featureName, '1', file=self.outFile)
2558 for section in self.TYPE_SECTIONS:
2559 #write('// endFeature writing section'+section, file=self.outFile)
2560 contents = self.sections[section]
2561 if contents:
2562 write('\n'.join(contents), file=self.outFile)
2563 self.newline()
2564 #write('// endFeature looking at self.sections[command]', file=self.outFile)
2565 if (self.sections['command']):
2566 write('\n'.join(self.sections['command']), end='', file=self.outFile)
2567 self.newline()
2568 if (self.featureExtraProtect != None):
2569 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
2570 if (self.genOpts.protectFeature):
2571 write('#endif /*', self.featureName, '*/', file=self.outFile)
2572 # Finish processing in superclass
2573 OutputGenerator.endFeature(self)
2574 #write('// ending endFeature', file=self.outFile)
2575 #
2576 # Append a definition to the specified section
2577 def appendSection(self, section, text):
2578 # self.sections[section].append('SECTION: ' + section + '\n')
2579 self.sections[section].append(text)
2580 #
2581 # Type generation
2582 def genType(self, typeinfo, name):
2583 pass
2584 #
2585 # Struct (e.g. C "struct" type) generation.
2586 # This is a special case of the <type> tag where the contents are
2587 # interpreted as a set of <member> tags instead of freeform C
2588 # C type declarations. The <member> tags are just like <param>
2589 # tags - they are a declaration of a struct or union member.
2590 # Only simple member declarations are supported (no nested
2591 # structs etc.)
2592 def genStruct(self, typeinfo, typeName):
2593 OutputGenerator.genStruct(self, typeinfo, typeName)
2594 body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
2595 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
2596 for member in typeinfo.elem.findall('.//member'):
2597 body += self.makeCParamDecl(member, self.genOpts.alignFuncParam)
2598 body += ';\n'
2599 body += '} ' + typeName + ';\n'
2600 self.appendSection('struct', body)
2601 #
2602 # Group (e.g. C "enum" type) generation.
2603 # These are concatenated together with other types.
2604 def genGroup(self, groupinfo, groupName):
2605 pass
2606 # Enumerant generation
2607 # <enum> tags may specify their values in several ways, but are usually
2608 # just integers.
2609 def genEnum(self, enuminfo, name):
2610 pass
2611 #
2612 # Command generation
2613 def genCmd(self, cmdinfo, name):
2614 special_functions = [
2615 'vkGetDeviceProcAddr',
2616 'vkGetInstanceProcAddr',
2617 'vkCreateDevice',
2618 'vkDestroyDevice',
2619 'vkCreateInstance',
2620 'vkDestroyInstance',
2621 'vkEnumerateInstanceLayerProperties',
2622 'vkEnumerateInstanceExtensionProperties',
2623 'vkAllocateCommandBuffers',
2624 'vkFreeCommandBuffers',
2625 'vkCreateDebugReportCallbackEXT',
2626 'vkDestroyDebugReportCallbackEXT',
2627 ]
2628 if name in special_functions:
Michael Lentine584ccab2016-02-03 16:51:46 -06002629 self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name) ]
Mike Stroyan8849f9a2015-11-02 15:30:20 -07002630 return
2631 if "KHR" in name:
2632 self.appendSection('command', '// TODO - not wrapping KHR function ' + name)
2633 return
2634 # Determine first if this function needs to be intercepted
2635 startthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'start')
2636 if startthreadsafety is None:
2637 return
2638 finishthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'finish')
2639 # record that the function will be intercepted
2640 if (self.featureExtraProtect != None):
2641 self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ]
Michael Lentine584ccab2016-02-03 16:51:46 -06002642 self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name) ]
Mike Stroyan8849f9a2015-11-02 15:30:20 -07002643 if (self.featureExtraProtect != None):
2644 self.intercepts += [ '#endif' ]
2645
2646 OutputGenerator.genCmd(self, cmdinfo, name)
2647 #
2648 decls = self.makeCDecls(cmdinfo.elem)
2649 self.appendSection('command', '')
2650 self.appendSection('command', decls[0][:-1])
2651 self.appendSection('command', '{')
2652 # setup common to call wrappers
2653 # first parameter is always dispatchable
2654 dispatchable_type = cmdinfo.elem.find('param/type').text
2655 dispatchable_name = cmdinfo.elem.find('param/name').text
2656 self.appendSection('command', ' dispatch_key key = get_dispatch_key('+dispatchable_name+');')
2657 self.appendSection('command', ' layer_data *my_data = get_my_data_ptr(key, layer_data_map);')
2658 if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]:
2659 self.appendSection('command', ' VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table;')
2660 else:
2661 self.appendSection('command', ' VkLayerDispatchTable *pTable = my_data->device_dispatch_table;')
2662 # Declare result variable, if any.
2663 resulttype = cmdinfo.elem.find('proto/type')
2664 if (resulttype != None and resulttype.text == 'void'):
2665 resulttype = None
2666 if (resulttype != None):
2667 self.appendSection('command', ' ' + resulttype.text + ' result;')
2668 assignresult = 'result = '
2669 else:
2670 assignresult = ''
2671
2672 self.appendSection('command', str(startthreadsafety))
2673 params = cmdinfo.elem.findall('param/name')
2674 paramstext = ','.join([str(param.text) for param in params])
2675 API = cmdinfo.elem.attrib.get('name').replace('vk','pTable->',1)
2676 self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');')
2677 self.appendSection('command', str(finishthreadsafety))
2678 # Return result variable, if any.
2679 if (resulttype != None):
2680 self.appendSection('command', ' return result;')
2681 self.appendSection('command', '}')
Dustin Gravesf69e3772016-02-11 10:10:14 -07002682
2683# ParamCheckerOutputGenerator - subclass of OutputGenerator.
2684# Generates param checker layer code.
2685#
2686# ---- methods ----
2687# ParamCheckerOutputGenerator(errFile, warnFile, diagFile) - args as for
2688# OutputGenerator. Defines additional internal state.
2689# ---- methods overriding base class ----
2690# beginFile(genOpts)
2691# endFile()
2692# beginFeature(interface, emit)
2693# endFeature()
2694# genType(typeinfo,name)
2695# genStruct(typeinfo,name)
2696# genGroup(groupinfo,name)
2697# genEnum(enuminfo, name)
2698# genCmd(cmdinfo)
2699class ParamCheckerOutputGenerator(OutputGenerator):
2700 """Generate ParamChecker code based on XML element attributes"""
2701 # This is an ordered list of sections in the header file.
2702 ALL_SECTIONS = ['command']
2703 def __init__(self,
2704 errFile = sys.stderr,
2705 warnFile = sys.stderr,
2706 diagFile = sys.stdout):
2707 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
2708 self.INDENT_SPACES = 4
2709 # Commands to ignore
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002710 self.blacklist = [
2711 'vkCreateInstance', 'vkCreateDevice',
2712 'vkGetInstanceProcAddr', 'vkGetDeviceProcAddr',
2713 'vkEnumerateInstanceLayerProperties',
2714 'vkEnumerateInstanceExtensionsProperties',
2715 'vkEnumerateDeviceLayerProperties',
2716 'vkEnumerateDeviceExtensionsProperties',
2717 'vkCreateDebugReportCallbackEXT',
2718 'vkDebugReportMessageEXT']
Dustin Gravesf69e3772016-02-11 10:10:14 -07002719 # Internal state - accumulators for different inner block text
2720 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002721 self.structNames = [] # List of Vulkan struct typenames
2722 self.stypes = [] # Values from the VkStructureType enumeration
2723 self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType
2724 self.commands = [] # List of CommandData records for all Vulkan commands
2725 self.structMembers = [] # List of StructMemberData records for all Vulkan structs
2726 self.validatedStructs = set() # Set of structs containing members that require validation
Dustin Gravesf69e3772016-02-11 10:10:14 -07002727 # Named tuples to store struct and command data
2728 self.StructType = namedtuple('StructType', ['name', 'value'])
2729 self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isstaticarray', 'isoptional', 'iscount', 'len', 'cdecl'])
2730 self.CommandData = namedtuple('CommandData', ['name', 'params', 'cdecl'])
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002731 self.StructMemberData = namedtuple('StructMemberData', ['name', 'members'])
Dustin Gravesf69e3772016-02-11 10:10:14 -07002732 #
2733 def incIndent(self, indent):
2734 inc = ' ' * self.INDENT_SPACES
2735 if indent:
2736 return indent + inc
2737 return inc
2738
2739 def decIndent(self, indent):
2740 if indent and (len(indent) > self.INDENT_SPACES):
2741 return indent[:-self.INDENT_SPACES]
2742 return ''
2743 #
2744 def beginFile(self, genOpts):
2745 OutputGenerator.beginFile(self, genOpts)
2746 # C-specific
2747 #
2748 # User-supplied prefix text, if any (list of strings)
2749 if (genOpts.prefixText):
2750 for s in genOpts.prefixText:
2751 write(s, file=self.outFile)
2752 #
2753 # Multiple inclusion protection & C++ wrappers.
2754 if (genOpts.protectFile and self.genOpts.filename):
2755 headerSym = re.sub('\.h', '_H', os.path.basename(self.genOpts.filename)).upper()
2756 write('#ifndef', headerSym, file=self.outFile)
2757 write('#define', headerSym, '1', file=self.outFile)
2758 self.newline()
2759 #
2760 # Headers
2761 write('#include "vulkan/vulkan.h"', file=self.outFile)
2762 write('#include "param_checker_utils.h"', file=self.outFile)
2763 #
2764 # Macros
2765 self.newline()
2766 write('#ifndef UNUSED_PARAMETER', file=self.outFile)
2767 write('#define UNUSED_PARAMETER(x) (void)(x)', file=self.outFile)
2768 write('#endif // UNUSED_PARAMETER', file=self.outFile)
2769 def endFile(self):
2770 # C-specific
2771 # Finish C++ wrapper and multiple inclusion protection
2772 self.newline()
2773 if (self.genOpts.protectFile and self.genOpts.filename):
2774 self.newline()
2775 write('#endif', file=self.outFile)
2776 # Finish processing in superclass
2777 OutputGenerator.endFile(self)
2778 def beginFeature(self, interface, emit):
2779 # Start processing in superclass
2780 OutputGenerator.beginFeature(self, interface, emit)
2781 # C-specific
2782 # Accumulate includes, defines, types, enums, function pointer typedefs,
2783 # end function prototypes separately for this feature. They're only
2784 # printed in endFeature().
2785 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002786 self.structNames = []
Dustin Gravesf69e3772016-02-11 10:10:14 -07002787 self.stypes = []
2788 self.structTypes = dict()
2789 self.commands = []
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002790 self.structMembers = []
2791 self.validatedStructs = set()
Dustin Gravesf69e3772016-02-11 10:10:14 -07002792 def endFeature(self):
2793 # C-specific
2794 # Actually write the interface to the output file.
2795 if (self.emit):
2796 self.newline()
2797 # If type declarations are needed by other features based on
2798 # this one, it may be necessary to suppress the ExtraProtect,
2799 # or move it below the 'for section...' loop.
2800 if (self.featureExtraProtect != None):
2801 write('#ifdef', self.featureExtraProtect, file=self.outFile)
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002802 # Generate the struct member checking code from the captured data
2803 self.prepareStructMemberData()
2804 self.processStructMemberData()
2805 # Generate the command parameter checking code from the captured data
Dustin Gravesf69e3772016-02-11 10:10:14 -07002806 self.processCmdData()
2807 if (self.sections['command']):
2808 if (self.genOpts.protectProto):
2809 write(self.genOpts.protectProto,
2810 self.genOpts.protectProtoStr, file=self.outFile)
2811 write('\n'.join(self.sections['command']), end='', file=self.outFile)
2812 if (self.featureExtraProtect != None):
2813 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
2814 else:
2815 self.newline()
2816 # Finish processing in superclass
2817 OutputGenerator.endFeature(self)
2818 #
2819 # Append a definition to the specified section
2820 def appendSection(self, section, text):
2821 # self.sections[section].append('SECTION: ' + section + '\n')
2822 self.sections[section].append(text)
2823 #
2824 # Type generation
2825 def genType(self, typeinfo, name):
2826 OutputGenerator.genType(self, typeinfo, name)
2827 typeElem = typeinfo.elem
2828 # If the type is a struct type, traverse the imbedded <member> tags
2829 # generating a structure. Otherwise, emit the tag text.
2830 category = typeElem.get('category')
2831 if (category == 'struct' or category == 'union'):
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002832 self.structNames.append(name)
Dustin Gravesf69e3772016-02-11 10:10:14 -07002833 self.genStruct(typeinfo, name)
2834 #
2835 # Struct parameter check generation.
2836 # This is a special case of the <type> tag where the contents are
2837 # interpreted as a set of <member> tags instead of freeform C
2838 # C type declarations. The <member> tags are just like <param>
2839 # tags - they are a declaration of a struct or union member.
2840 # Only simple member declarations are supported (no nested
2841 # structs etc.)
2842 def genStruct(self, typeinfo, typeName):
2843 OutputGenerator.genStruct(self, typeinfo, typeName)
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002844 members = typeinfo.elem.findall('.//member')
2845 #
2846 # Iterate over members once to get length parameters for arrays
2847 lens = set()
2848 for member in members:
2849 len = self.getLen(member)
2850 if len:
2851 lens.add(len)
2852 #
2853 # Generate member info
2854 membersInfo = []
2855 for member in members:
Dustin Gravesf69e3772016-02-11 10:10:14 -07002856 # Get the member's type and name
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002857 info = self.getTypeNameTuple(member)
2858 type = info[0]
2859 name = info[1]
2860 stypeValue = ''
Dustin Gravesf69e3772016-02-11 10:10:14 -07002861 # Process VkStructureType
2862 if type == 'VkStructureType':
2863 # Extract the required struct type value from the comments
2864 # embedded in the original text defining the 'typeinfo' element
2865 rawXml = etree.tostring(typeinfo.elem).decode('ascii')
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002866 result = re.search(r'VK_STRUCTURE_TYPE_\w+', rawXml)
Dustin Gravesf69e3772016-02-11 10:10:14 -07002867 if result:
2868 value = result.group(0)
2869 # Make sure value is valid
2870 #if value not in self.stypes:
2871 # print('WARNING: {} is not part of the VkStructureType enumeration [{}]'.format(value, typeName))
2872 else:
2873 value = '<ERROR>'
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002874 # Store the required type value
Dustin Gravesf69e3772016-02-11 10:10:14 -07002875 self.structTypes[typeName] = self.StructType(name=name, value=value)
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002876 #
2877 # Store pointer/array/string info
2878 # Check for parameter name in lens set
2879 iscount = False
2880 if name in lens:
2881 iscount = True
2882 # The pNext members are not tagged as optional, but are treated as
2883 # optional for parameter NULL checks. Static array members
2884 # are also treated as optional to skip NULL pointer validation, as
2885 # they won't be NULL.
2886 isstaticarray = self.paramIsStaticArray(member)
2887 isoptional = False
2888 if self.paramIsOptional(member) or (name == 'pNext') or (isstaticarray):
2889 isoptional = True
2890 membersInfo.append(self.CommandParam(type=type, name=name,
2891 ispointer=self.paramIsPointer(member),
2892 isstaticarray=isstaticarray,
2893 isoptional=isoptional,
2894 iscount=iscount,
2895 len=self.getLen(member),
2896 cdecl=self.makeCParamDecl(member, 0)))
2897 self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo))
Dustin Gravesf69e3772016-02-11 10:10:14 -07002898 #
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002899 # Capture group (e.g. C "enum" type) info to be used for
2900 # param check code generation.
Dustin Gravesf69e3772016-02-11 10:10:14 -07002901 # These are concatenated together with other types.
2902 def genGroup(self, groupinfo, groupName):
2903 OutputGenerator.genGroup(self, groupinfo, groupName)
2904 if groupName == 'VkStructureType':
2905 groupElem = groupinfo.elem
2906 for elem in groupElem.findall('enum'):
2907 name = elem.get('name')
2908 self.stypes.append(name)
2909 #
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002910 # Capture command parameter info to be used for param
2911 # check code generation.
Dustin Gravesf69e3772016-02-11 10:10:14 -07002912 def genCmd(self, cmdinfo, name):
2913 OutputGenerator.genCmd(self, cmdinfo, name)
2914 if name not in self.blacklist:
Dustin Gravesf69e3772016-02-11 10:10:14 -07002915 params = cmdinfo.elem.findall('param')
Dustin Gravesf69e3772016-02-11 10:10:14 -07002916 # Get list of array lengths
2917 lens = set()
2918 for param in params:
2919 len = self.getLen(param)
2920 if len:
2921 lens.add(len)
2922 # Get param info
2923 paramsInfo = []
2924 for param in params:
2925 paramInfo = self.getTypeNameTuple(param)
2926 # Check for parameter name in lens set
2927 iscount = False
2928 if paramInfo[1] in lens:
2929 iscount = True
2930 paramsInfo.append(self.CommandParam(type=paramInfo[0], name=paramInfo[1],
2931 ispointer=self.paramIsPointer(param),
2932 isstaticarray=self.paramIsStaticArray(param),
2933 isoptional=self.paramIsOptional(param),
2934 iscount=iscount,
2935 len=self.getLen(param),
2936 cdecl=self.makeCParamDecl(param, 0)))
2937 self.commands.append(self.CommandData(name=name, params=paramsInfo, cdecl=self.makeCDecls(cmdinfo.elem)[0]))
2938 #
2939 # Check if the parameter passed in is a pointer
2940 def paramIsPointer(self, param):
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002941 ispointer = 0
Dustin Gravesf69e3772016-02-11 10:10:14 -07002942 paramtype = param.find('type')
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002943 if (paramtype.tail is not None) and ('*' in paramtype.tail):
2944 ispointer = paramtype.tail.count('*')
Dustin Gravesf69e3772016-02-11 10:10:14 -07002945 return ispointer
2946 #
2947 # Check if the parameter passed in is a static array
2948 def paramIsStaticArray(self, param):
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002949 isstaticarray = 0
2950 paramname = param.find('name')
2951 if (paramname.tail is not None) and ('[' in paramname.tail):
2952 isstaticarray = paramname.tail.count('[')
Dustin Gravesf69e3772016-02-11 10:10:14 -07002953 return isstaticarray
2954 #
2955 # Check if the parameter passed in is optional
2956 # Returns a list of Boolean values for comma separated len attributes (len='false,true')
2957 def paramIsOptional(self, param):
2958 # See if the handle is optional
2959 isoptional = False
2960 # Simple, if it's optional, return true
2961 optString = param.attrib.get('optional')
2962 if optString:
2963 if optString == 'true':
2964 isoptional = True
2965 elif ',' in optString:
2966 opts = []
2967 for opt in optString.split(','):
2968 val = opt.strip()
2969 if val == 'true':
2970 opts.append(True)
2971 elif val == 'false':
2972 opts.append(False)
2973 else:
2974 print('Unrecognized len attribute value',val)
2975 isoptional = opts
2976 return isoptional
2977 #
2978 # Retrieve the value of the len tag
2979 def getLen(self, param):
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002980 result = None
Dustin Gravesf69e3772016-02-11 10:10:14 -07002981 len = param.attrib.get('len')
2982 if len and len != 'null-terminated':
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07002983 # For string arrays, 'len' can look like 'count,null-terminated',
2984 # indicating that we have a null terminated array of strings. We
2985 # strip the null-terminated from the 'len' field and only return
2986 # the parameter specifying the string count
2987 if 'null-terminated' in len:
2988 result = len.split(',')[0]
2989 else:
2990 result = len
2991 return result
Dustin Gravesf69e3772016-02-11 10:10:14 -07002992 #
2993 # Retrieve the type and name for a parameter
2994 def getTypeNameTuple(self, param):
2995 type = ''
2996 name = ''
2997 for elem in param:
2998 if elem.tag == 'type':
2999 type = noneStr(elem.text)
3000 elif elem.tag == 'name':
3001 name = noneStr(elem.text)
3002 return (type, name)
3003 #
3004 # Find a named parameter in a parameter list
3005 def getParamByName(self, params, name):
3006 for param in params:
3007 if param.name == name:
3008 return param
3009 return None
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07003010 #
3011 # Get the length paramater record for the specified parameter name
3012 def getLenParam(self, params, name):
3013 lenParam = None
3014 if name:
3015 if '->' in name:
3016 # The count is obtained by dereferencing a member of a struct parameter
3017 lenParam = self.CommandParam(name=name, iscount=True, ispointer=False, isoptional=False, type=None, len=None, isstaticarray=None, cdecl=None)
3018 elif 'latexmath' in name:
3019 result = re.search('mathit\{(\w+)\}', name)
3020 lenParam = self.getParamByName(params, result.group(1))
3021 elif '/' in name:
3022 # Len specified as an equation such as dataSize/4
3023 lenParam = self.getParamByName(params, name.split('/')[0])
3024 else:
3025 lenParam = self.getParamByName(params, name)
3026 return lenParam
3027 #
3028 # Convert a vulkan.h command declaration into a param_check.h definition
Dustin Gravesf69e3772016-02-11 10:10:14 -07003029 def getCmdDef(self, cmd):
Dustin Gravesf69e3772016-02-11 10:10:14 -07003030 #
3031 # Strip the trailing ';' and split into individual lines
3032 lines = cmd.cdecl[:-1].split('\n')
3033 # Replace Vulkan prototype
3034 lines[0] = 'static VkBool32 param_check_' + cmd.name + '('
3035 # Replace the first argument with debug_report_data
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07003036 lines[1] = ' debug_report_data*'.ljust(self.genOpts.alignFuncParam) + 'report_data,'
Dustin Gravesf69e3772016-02-11 10:10:14 -07003037 return '\n'.join(lines)
3038 #
Dustin Gravesaf1f1b82016-02-29 13:35:07 -07003039 # Generate the code to check for a NULL dereference before calling the
3040 # validation function
3041 def genCheckedLengthCall(self, indent, name, expr):
3042 count = name.count('->')
3043 if count:
3044 checkedExpr = ''
3045 localIndent = indent
3046 elements = name.split('->')
3047 # Open the if expression blocks
3048 for i in range(0, count):
3049 checkedExpr += localIndent + 'if ({} != NULL) {{\n'.format('->'.join(elements[0:i+1]))
3050 localIndent = self.incIndent(localIndent)
3051 # Add the validation expression
3052 checkedExpr += localIndent + expr
3053 # Close the if blocks
3054 for i in range(0, count):
3055 localIndent = self.decIndent(localIndent)
3056 checkedExpr += localIndent + '}\n'
3057 return checkedExpr
3058 # No if statements were required
3059 return indent + expr
Dustin Gravesaf0d6dc2016-03-02 18:23:29 -07003060 #
3061 # Generate the parameter checking code
3062 def genFuncBody(self, indent, name, values, valuePrefix, variablePrefix):
3063 funcBody = ''
3064 unused = []
3065 for value in values:
3066 checkExpr = '' # Code to check the current parameter
3067 #
3068 # Check for NULL pointers, ignore the inout count parameters that
3069 # will be validated with their associated array
3070 if (value.ispointer or value.isstaticarray) and not value.iscount:
3071 #
3072 # Generate the full name of the value, which will be printed in
3073 # the error message, by adding the variable prefix to the
3074 # value name
3075 valueDisplayName = '(std::string({}) + std::string("{}")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}"'.format(value.name)
3076 #
3077 # Parameters for function argument generation
3078 req = 'VK_TRUE' # Paramerter can be NULL
3079 cpReq = 'VK_TRUE' # Count pointer can be NULL
3080 cvReq = 'VK_TRUE' # Count value can be 0
3081 lenParam = None
3082 #
3083 # Generate required/optional parameter strings for the pointer and count values
3084 if value.isoptional:
3085 req = 'VK_FALSE'
3086 if value.len:
3087 # The parameter is an array with an explicit count parameter
3088 lenParam = self.getLenParam(values, value.len)
3089 if not lenParam: print(value.len)
3090 if lenParam.ispointer:
3091 # Count parameters that are pointers are inout
3092 if type(lenParam.isoptional) is list:
3093 if lenParam.isoptional[0]:
3094 cpReq = 'VK_FALSE'
3095 if lenParam.isoptional[1]:
3096 cvReq = 'VK_FALSE'
3097 else:
3098 if lenParam.isoptional:
3099 cpReq = 'VK_FALSE'
3100 else:
3101 if lenParam.isoptional:
3102 cvReq = 'VK_FALSE'
3103 #
3104 # If this is a pointer to a struct with an sType field, verify the type
3105 if value.type in self.structTypes:
3106 stype = self.structTypes[value.type]
3107 if lenParam:
3108 # This is an array
3109 if lenParam.ispointer:
3110 # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required
3111 checkExpr = 'skipCall |= validate_struct_type_array(report_data, {}, "{ln}", {dn}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {}, {});\n'.format(name, cpReq, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, sv=stype.value, pf=valuePrefix)
3112 else:
3113 checkExpr = 'skipCall |= validate_struct_type_array(report_data, {}, "{ln}", {dn}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {});\n'.format(name, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, sv=stype.value, pf=valuePrefix)
3114 else:
3115 checkExpr = 'skipCall |= validate_struct_type(report_data, {}, {}, "{sv}", {}{vn}, {sv}, {});\n'.format(name, valueDisplayName, valuePrefix, req, vn=value.name, sv=stype.value)
3116 else:
3117 if lenParam:
3118 # This is an array
3119 if lenParam.ispointer:
3120 # If count and array parameters are optional, there
3121 # will be no validation
3122 if req == 'VK_TRUE' or cpReq == 'VK_TRUE' or cvReq == 'VK_TRUE':
3123 # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required
3124 checkExpr = 'skipCall |= validate_array(report_data, {}, "{ln}", {dn}, {pf}{ln}, {pf}{vn}, {}, {}, {});\n'.format(name, cpReq, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, pf=valuePrefix)
3125 else:
3126 # If count and array parameters are optional, there
3127 # will be no validation
3128 if req == 'VK_TRUE' or cvReq == 'VK_TRUE':
3129 checkExpr = 'skipCall |= validate_array(report_data, {}, "{ln}", {dn}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format(name, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, pf=valuePrefix)
3130 elif not value.isoptional:
3131 checkExpr = 'skipCall |= validate_required_pointer(report_data, {}, {}, {}{vn});\n'.format(name, valueDisplayName, valuePrefix, vn=value.name)
3132 else:
3133 unused.append(value.name)
3134 #
3135 # If this is a pointer to a struct, see if it contains members
3136 # that need to be checked
3137 if value.type in self.validatedStructs:
3138 if checkExpr:
3139 checkExpr += '\n' + indent
3140 #
3141 # The name prefix used when reporting an error with a struct member (eg. the 'pCreateInfor->' in 'pCreateInfo->sType')
3142 prefix = '(std::string({}) + std::string("{}->")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}->"'.format(value.name)
3143 checkExpr += 'skipCall |= param_check_{}(report_data, {}, {}, {}{});\n'.format(value.type, name, prefix, valuePrefix, value.name)
3144 elif value.type in self.validatedStructs:
3145 # The name prefix used when reporting an error with a struct member (eg. the 'pCreateInfor->' in 'pCreateInfo->sType')
3146 prefix = '(std::string({}) + std::string("{}.")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}."'.format(value.name)
3147 checkExpr += 'skipCall |= param_check_{}(report_data, {}, {}, &({}{}));\n'.format(value.type, name, prefix, valuePrefix, value.name)
3148 elif not value.iscount:
3149 unused.append(value.name)
3150 #
3151 # Append the parameter check to the function body for the current command
3152 if checkExpr:
3153 funcBody += '\n'
3154 if lenParam and ('->' in lenParam.name):
3155 # Add checks to ensure the validation call does not dereference a NULL pointer to obtain the count
3156 funcBody += self.genCheckedLengthCall(indent, lenParam.name, checkExpr)
3157 else:
3158 funcBody += indent + checkExpr
3159 return funcBody, unused
3160 #
3161 # Post-process the collected struct member data to create a list of structs
3162 # with members that need to be validated
3163 def prepareStructMemberData(self):
3164 for struct in self.structMembers:
3165 for member in struct.members:
3166 if not member.iscount:
3167 lenParam = self.getLenParam(struct.members, member.len)
3168 # The sType needs to be validated
3169 # An required array/count needs to be validated
3170 # A required pointer needs to be validated
3171 validated = False
3172 if member.type in self.structTypes:
3173 validated = True
3174 elif member.ispointer and lenParam: # This is an array
3175 # Make sure len is not optional
3176 if lenParam.ispointer:
3177 if not lenParam.isoptional[0] or not lenParam.isoptional[1] or not member.isoptional:
3178 validated = True
3179 else:
3180 if not lenParam.isoptional or not member.isoptional:
3181 validated = True
3182 elif member.ispointer and not member.isoptional:
3183 validated = True
3184 #
3185 if validated:
3186 self.validatedStructs.add(struct.name)
3187 # Second pass to check for struct members that are structs
3188 # requiring validation
3189 for member in struct.members:
3190 if member.type in self.validatedStructs:
3191 self.validatedStructs.add(struct.name)
3192 #
3193 # Generate the struct member check code from the captured data
3194 def processStructMemberData(self):
3195 indent = self.incIndent(None)
3196 for struct in self.structMembers:
3197 # The string returned by genFuncBody will be nested in an if check
3198 # for a NULL pointer, so needs its indent incremented
3199 funcBody, unused = self.genFuncBody(self.incIndent(indent), 'pFuncName', struct.members, 'pStruct->', 'pVariableName')
3200 if funcBody:
3201 cmdDef = 'static VkBool32 param_check_{}(\n'.format(struct.name)
3202 cmdDef += ' debug_report_data*'.ljust(self.genOpts.alignFuncParam) + ' report_data,\n'
3203 cmdDef += ' const char*'.ljust(self.genOpts.alignFuncParam) + ' pFuncName,\n'
3204 cmdDef += ' const char*'.ljust(self.genOpts.alignFuncParam) + ' pVariableName,\n'
3205 cmdDef += ' const {}*'.format(struct.name).ljust(self.genOpts.alignFuncParam) + ' pStruct)\n'
3206 cmdDef += '{\n'
3207 cmdDef += indent + 'VkBool32 skipCall = VK_FALSE;\n'
3208 cmdDef += '\n'
3209 cmdDef += indent + 'if (pStruct != NULL) {'
3210 cmdDef += funcBody
3211 cmdDef += indent +'}\n'
3212 cmdDef += '\n'
3213 cmdDef += indent + 'return skipCall;\n'
3214 cmdDef += '}\n'
3215 self.appendSection('command', cmdDef)
3216 #
3217 # Generate the command param check code from the captured data
3218 def processCmdData(self):
3219 indent = self.incIndent(None)
3220 for command in self.commands:
3221 cmdBody, unused = self.genFuncBody(indent, '"{}"'.format(command.name), command.params, '', None)
3222 if cmdBody:
3223 cmdDef = self.getCmdDef(command) + '\n'
3224 cmdDef += '{\n'
3225 # Process unused parameters
3226 # Ignore the first dispatch handle parameter, which is not
3227 # processed by param_check
3228 for name in unused[1:]:
3229 cmdDef += indent + 'UNUSED_PARAMETER({});\n'.format(name)
3230 if len(unused) > 1:
3231 cmdDef += '\n'
3232 cmdDef += indent + 'VkBool32 skipCall = VK_FALSE;\n'
3233 cmdDef += cmdBody
3234 cmdDef += '\n'
3235 cmdDef += indent + 'return skipCall;\n'
3236 cmdDef += '}\n'
3237 self.appendSection('command', cmdDef)
Dustin Gravesaf1f1b82016-02-29 13:35:07 -07003238
Dustin Gravesf69e3772016-02-11 10:10:14 -07003239