1#!/usr/bin/python3 -i
2#
3# Copyright (c) 2015-2016 The Khronos Group Inc.
4# Copyright (c) 2015-2016 Valve Corporation
5# Copyright (c) 2015-2016 LunarG, Inc.
6# Copyright (c) 2015-2016 Google Inc.
7#
8# Licensed under the Apache License, Version 2.0 (the "License");
9# you may not use this file except in compliance with the License.
10# You may obtain a copy of the License at
11#
12#     http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS,
16# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19#
20# Author: Mike Stroyan <stroyan@google.com>
21
22import os,re,sys
23from generator import *
24
25# ThreadGeneratorOptions - subclass of GeneratorOptions.
26#
27# Adds options used by ThreadOutputGenerator objects during threading
28# layer generation.
29#
30# Additional members
31#   prefixText - list of strings to prefix generated header with
32#     (usually a copyright statement + calling convention macros).
33#   protectFile - True if multiple inclusion protection should be
34#     generated (based on the filename) around the entire header.
35#   protectFeature - True if #ifndef..#endif protection should be
36#     generated around a feature interface in the header file.
37#   genFuncPointers - True if function pointer typedefs should be
38#     generated
39#   protectProto - If conditional protection should be generated
40#     around prototype declarations, set to either '#ifdef'
41#     to require opt-in (#ifdef protectProtoStr) or '#ifndef'
42#     to require opt-out (#ifndef protectProtoStr). Otherwise
43#     set to None.
44#   protectProtoStr - #ifdef/#ifndef symbol to use around prototype
45#     declarations, if protectProto is set
46#   apicall - string to use for the function declaration prefix,
47#     such as APICALL on Windows.
48#   apientry - string to use for the calling convention macro,
49#     in typedefs, such as APIENTRY.
50#   apientryp - string to use for the calling convention macro
51#     in function pointer typedefs, such as APIENTRYP.
52#   indentFuncProto - True if prototype declarations should put each
53#     parameter on a separate line
54#   indentFuncPointer - True if typedefed function pointers should put each
55#     parameter on a separate line
56#   alignFuncParam - if nonzero and parameters are being put on a
57#     separate line, align parameter names at the specified column
58class ThreadGeneratorOptions(GeneratorOptions):
59    def __init__(self,
60                 filename = None,
61                 directory = '.',
62                 apiname = None,
63                 profile = None,
64                 versions = '.*',
65                 emitversions = '.*',
66                 defaultExtensions = None,
67                 addExtensions = None,
68                 removeExtensions = None,
69                 sortProcedure = regSortFeatures,
70                 prefixText = "",
71                 genFuncPointers = True,
72                 protectFile = True,
73                 protectFeature = True,
74                 protectProto = None,
75                 protectProtoStr = None,
76                 apicall = '',
77                 apientry = '',
78                 apientryp = '',
79                 indentFuncProto = True,
80                 indentFuncPointer = False,
81                 alignFuncParam = 0):
82        GeneratorOptions.__init__(self, filename, directory, apiname, profile,
83                                  versions, emitversions, defaultExtensions,
84                                  addExtensions, removeExtensions, sortProcedure)
85        self.prefixText      = prefixText
86        self.genFuncPointers = genFuncPointers
87        self.protectFile     = protectFile
88        self.protectFeature  = protectFeature
89        self.protectProto    = protectProto
90        self.protectProtoStr = protectProtoStr
91        self.apicall         = apicall
92        self.apientry        = apientry
93        self.apientryp       = apientryp
94        self.indentFuncProto = indentFuncProto
95        self.indentFuncPointer = indentFuncPointer
96        self.alignFuncParam  = alignFuncParam
97
98# ThreadOutputGenerator - subclass of OutputGenerator.
99# Generates Thread checking framework
100#
101# ---- methods ----
102# ThreadOutputGenerator(errFile, warnFile, diagFile) - args as for
103#   OutputGenerator. Defines additional internal state.
104# ---- methods overriding base class ----
105# beginFile(genOpts)
106# endFile()
107# beginFeature(interface, emit)
108# endFeature()
109# genType(typeinfo,name)
110# genStruct(typeinfo,name)
111# genGroup(groupinfo,name)
112# genEnum(enuminfo, name)
113# genCmd(cmdinfo)
114class ThreadOutputGenerator(OutputGenerator):
115    """Generate specified API interfaces in a specific style, such as a C header"""
116    # This is an ordered list of sections in the header file.
117    TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
118                     'group', 'bitmask', 'funcpointer', 'struct']
119    ALL_SECTIONS = TYPE_SECTIONS + ['command']
120    def __init__(self,
121                 errFile = sys.stderr,
122                 warnFile = sys.stderr,
123                 diagFile = sys.stdout):
124        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
125        # Internal state - accumulators for different inner block text
126        self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
127        self.intercepts = []
128
129    # Check if the parameter passed in is a pointer to an array
130    def paramIsArray(self, param):
131        return param.attrib.get('len') is not None
132
133    # Check if the parameter passed in is a pointer
134    def paramIsPointer(self, param):
135        ispointer = False
136        for elem in param:
137            #write('paramIsPointer '+elem.text, file=sys.stderr)
138            #write('elem.tag '+elem.tag, file=sys.stderr)
139            #if (elem.tail is None):
140            #    write('elem.tail is None', file=sys.stderr)
141            #else:
142            #    write('elem.tail '+elem.tail, file=sys.stderr)
143            if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail:
144                ispointer = True
145            #    write('is pointer', file=sys.stderr)
146        return ispointer
147    def makeThreadUseBlock(self, cmd, functionprefix):
148        """Generate C function pointer typedef for <command> Element"""
149        paramdecl = ''
150        thread_check_dispatchable_objects = [
151            "VkCommandBuffer",
152            "VkDevice",
153            "VkInstance",
154            "VkQueue",
155        ]
156        thread_check_nondispatchable_objects = [
157            "VkBuffer",
158            "VkBufferView",
159            "VkCommandPool",
160            "VkDescriptorPool",
161            "VkDescriptorSetLayout",
162            "VkDeviceMemory",
163            "VkEvent",
164            "VkFence",
165            "VkFramebuffer",
166            "VkImage",
167            "VkImageView",
168            "VkPipeline",
169            "VkPipelineCache",
170            "VkPipelineLayout",
171            "VkQueryPool",
172            "VkRenderPass",
173            "VkSampler",
174            "VkSemaphore",
175            "VkShaderModule",
176        ]
177
178        # Find and add any parameters that are thread unsafe
179        params = cmd.findall('param')
180        for param in params:
181            paramname = param.find('name')
182            if False: # self.paramIsPointer(param):
183                paramdecl += '    // not watching use of pointer ' + paramname.text + '\n'
184            else:
185                externsync = param.attrib.get('externsync')
186                if externsync == 'true':
187                    if self.paramIsArray(param):
188                        paramdecl += '    for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n'
189                        paramdecl += '        ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + '[index]);\n'
190                        paramdecl += '    }\n'
191                    else:
192                        paramdecl += '    ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + ');\n'
193                elif (param.attrib.get('externsync')):
194                    if self.paramIsArray(param):
195                        # Externsync can list pointers to arrays of members to synchronize
196                        paramdecl += '    for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n'
197                        for member in externsync.split(","):
198                            # Replace first empty [] in member name with index
199                            element = member.replace('[]','[index]',1)
200                            if '[]' in element:
201                                # Replace any second empty [] in element name with
202                                # inner array index based on mapping array names like
203                                # "pSomeThings[]" to "someThingCount" array size.
204                                # This could be more robust by mapping a param member
205                                # name to a struct type and "len" attribute.
206                                limit = element[0:element.find('s[]')] + 'Count'
207                                dotp = limit.rfind('.p')
208                                limit = limit[0:dotp+1] + limit[dotp+2:dotp+3].lower() + limit[dotp+3:]
209                                paramdecl += '        for(uint32_t index2=0;index2<'+limit+';index2++)\n'
210                                element = element.replace('[]','[index2]')
211                            paramdecl += '            ' + functionprefix + 'WriteObject(my_data, ' + element + ');\n'
212                        paramdecl += '    }\n'
213                    else:
214                        # externsync can list members to synchronize
215                        for member in externsync.split(","):
216                            member = str(member).replace("::", "->")
217                            paramdecl += '    ' + functionprefix + 'WriteObject(my_data, ' + member + ');\n'
218                else:
219                    paramtype = param.find('type')
220                    if paramtype is not None:
221                        paramtype = paramtype.text
222                    else:
223                        paramtype = 'None'
224                    if paramtype in thread_check_dispatchable_objects or paramtype in thread_check_nondispatchable_objects:
225                        if self.paramIsArray(param) and ('pPipelines' != paramname.text):
226                            paramdecl += '    for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n'
227                            paramdecl += '        ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + '[index]);\n'
228                            paramdecl += '    }\n'
229                        elif not self.paramIsPointer(param):
230                            # Pointer params are often being created.
231                            # They are not being read from.
232                            paramdecl += '    ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + ');\n'
233        explicitexternsyncparams = cmd.findall("param[@externsync]")
234        if (explicitexternsyncparams is not None):
235            for param in explicitexternsyncparams:
236                externsyncattrib = param.attrib.get('externsync')
237                paramname = param.find('name')
238                paramdecl += '    // Host access to '
239                if externsyncattrib == 'true':
240                    if self.paramIsArray(param):
241                        paramdecl += 'each member of ' + paramname.text
242                    elif self.paramIsPointer(param):
243                        paramdecl += 'the object referenced by ' + paramname.text
244                    else:
245                        paramdecl += paramname.text
246                else:
247                    paramdecl += externsyncattrib
248                paramdecl += ' must be externally synchronized\n'
249
250        # Find and add any "implicit" parameters that are thread unsafe
251        implicitexternsyncparams = cmd.find('implicitexternsyncparams')
252        if (implicitexternsyncparams is not None):
253            for elem in implicitexternsyncparams:
254                paramdecl += '    // '
255                paramdecl += elem.text
256                paramdecl += ' must be externally synchronized between host accesses\n'
257
258        if (paramdecl == ''):
259            return None
260        else:
261            return paramdecl
262    def beginFile(self, genOpts):
263        OutputGenerator.beginFile(self, genOpts)
264        # C-specific
265        #
266        # Multiple inclusion protection & C++ namespace.
267        if (genOpts.protectFile and self.genOpts.filename):
268            headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
269            write('#ifndef', headerSym, file=self.outFile)
270            write('#define', headerSym, '1', file=self.outFile)
271            self.newline()
272        write('namespace threading {', file=self.outFile)
273        self.newline()
274        #
275        # User-supplied prefix text, if any (list of strings)
276        if (genOpts.prefixText):
277            for s in genOpts.prefixText:
278                write(s, file=self.outFile)
279    def endFile(self):
280        # C-specific
281        # Finish C++ namespace and multiple inclusion protection
282        self.newline()
283        # record intercepted procedures
284        write('// intercepts', file=self.outFile)
285        write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile)
286        write('\n'.join(self.intercepts), file=self.outFile)
287        write('};\n', file=self.outFile)
288        self.newline()
289        write('} // namespace threading', file=self.outFile)
290        if (self.genOpts.protectFile and self.genOpts.filename):
291            self.newline()
292            write('#endif', file=self.outFile)
293        # Finish processing in superclass
294        OutputGenerator.endFile(self)
295    def beginFeature(self, interface, emit):
296        #write('// starting beginFeature', file=self.outFile)
297        # Start processing in superclass
298        OutputGenerator.beginFeature(self, interface, emit)
299        # C-specific
300        # Accumulate includes, defines, types, enums, function pointer typedefs,
301        # end function prototypes separately for this feature. They're only
302        # printed in endFeature().
303        self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
304        #write('// ending beginFeature', file=self.outFile)
305    def endFeature(self):
306        # C-specific
307        # Actually write the interface to the output file.
308        #write('// starting endFeature', file=self.outFile)
309        if (self.emit):
310            self.newline()
311            if (self.genOpts.protectFeature):
312                write('#ifndef', self.featureName, file=self.outFile)
313            # If type declarations are needed by other features based on
314            # this one, it may be necessary to suppress the ExtraProtect,
315            # or move it below the 'for section...' loop.
316            #write('// endFeature looking at self.featureExtraProtect', file=self.outFile)
317            if (self.featureExtraProtect != None):
318                write('#ifdef', self.featureExtraProtect, file=self.outFile)
319            #write('#define', self.featureName, '1', file=self.outFile)
320            for section in self.TYPE_SECTIONS:
321                #write('// endFeature writing section'+section, file=self.outFile)
322                contents = self.sections[section]
323                if contents:
324                    write('\n'.join(contents), file=self.outFile)
325                    self.newline()
326            #write('// endFeature looking at self.sections[command]', file=self.outFile)
327            if (self.sections['command']):
328                write('\n'.join(self.sections['command']), end='', file=self.outFile)
329                self.newline()
330            if (self.featureExtraProtect != None):
331                write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
332            if (self.genOpts.protectFeature):
333                write('#endif /*', self.featureName, '*/', file=self.outFile)
334        # Finish processing in superclass
335        OutputGenerator.endFeature(self)
336        #write('// ending endFeature', file=self.outFile)
337    #
338    # Append a definition to the specified section
339    def appendSection(self, section, text):
340        # self.sections[section].append('SECTION: ' + section + '\n')
341        self.sections[section].append(text)
342    #
343    # Type generation
344    def genType(self, typeinfo, name):
345        pass
346    #
347    # Struct (e.g. C "struct" type) generation.
348    # This is a special case of the <type> tag where the contents are
349    # interpreted as a set of <member> tags instead of freeform C
350    # C type declarations. The <member> tags are just like <param>
351    # tags - they are a declaration of a struct or union member.
352    # Only simple member declarations are supported (no nested
353    # structs etc.)
354    def genStruct(self, typeinfo, typeName):
355        OutputGenerator.genStruct(self, typeinfo, typeName)
356        body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
357        # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
358        for member in typeinfo.elem.findall('.//member'):
359            body += self.makeCParamDecl(member, self.genOpts.alignFuncParam)
360            body += ';\n'
361        body += '} ' + typeName + ';\n'
362        self.appendSection('struct', body)
363    #
364    # Group (e.g. C "enum" type) generation.
365    # These are concatenated together with other types.
366    def genGroup(self, groupinfo, groupName):
367        pass
368    # Enumerant generation
369    # <enum> tags may specify their values in several ways, but are usually
370    # just integers.
371    def genEnum(self, enuminfo, name):
372        pass
373    #
374    # Command generation
375    def genCmd(self, cmdinfo, name):
376        # Commands shadowed by interface functions and are not implemented
377        interface_functions = [
378            'vkEnumerateInstanceLayerProperties',
379            'vkEnumerateInstanceExtensionProperties',
380            'vkEnumerateDeviceLayerProperties',
381        ]
382        if name in interface_functions:
383            return
384        special_functions = [
385            'vkGetDeviceProcAddr',
386            'vkGetInstanceProcAddr',
387            'vkCreateDevice',
388            'vkDestroyDevice',
389            'vkCreateInstance',
390            'vkDestroyInstance',
391            'vkAllocateCommandBuffers',
392            'vkFreeCommandBuffers',
393            'vkCreateDebugReportCallbackEXT',
394            'vkDestroyDebugReportCallbackEXT',
395        ]
396        if name in special_functions:
397            decls = self.makeCDecls(cmdinfo.elem)
398            self.appendSection('command', '')
399            self.appendSection('command', '// declare only')
400            self.appendSection('command', decls[0])
401            self.intercepts += [ '    {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name[2:]) ]
402            return
403        if "KHR" in name:
404            self.appendSection('command', '// TODO - not wrapping KHR function ' + name)
405            return
406        if ("DebugMarker" in name) and ("EXT" in name):
407            self.appendSection('command', '// TODO - not wrapping EXT function ' + name)
408            return
409        # Determine first if this function needs to be intercepted
410        startthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'start')
411        if startthreadsafety is None:
412            return
413        finishthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'finish')
414        # record that the function will be intercepted
415        if (self.featureExtraProtect != None):
416            self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ]
417        self.intercepts += [ '    {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name[2:]) ]
418        if (self.featureExtraProtect != None):
419            self.intercepts += [ '#endif' ]
420
421        OutputGenerator.genCmd(self, cmdinfo, name)
422        #
423        decls = self.makeCDecls(cmdinfo.elem)
424        self.appendSection('command', '')
425        self.appendSection('command', decls[0][:-1])
426        self.appendSection('command', '{')
427        # setup common to call wrappers
428        # first parameter is always dispatchable
429        dispatchable_type = cmdinfo.elem.find('param/type').text
430        dispatchable_name = cmdinfo.elem.find('param/name').text
431        self.appendSection('command', '    dispatch_key key = get_dispatch_key('+dispatchable_name+');')
432        self.appendSection('command', '    layer_data *my_data = get_my_data_ptr(key, layer_data_map);')
433        if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]:
434            self.appendSection('command', '    VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table;')
435        else:
436            self.appendSection('command', '    VkLayerDispatchTable *pTable = my_data->device_dispatch_table;')
437        # Declare result variable, if any.
438        resulttype = cmdinfo.elem.find('proto/type')
439        if (resulttype != None and resulttype.text == 'void'):
440          resulttype = None
441        if (resulttype != None):
442            self.appendSection('command', '    ' + resulttype.text + ' result;')
443            assignresult = 'result = '
444        else:
445            assignresult = ''
446
447        self.appendSection('command', '    bool threadChecks = startMultiThread();')
448        self.appendSection('command', '    if (threadChecks) {')
449        self.appendSection('command', "    "+"\n    ".join(str(startthreadsafety).rstrip().split("\n")))
450        self.appendSection('command', '    }')
451        params = cmdinfo.elem.findall('param/name')
452        paramstext = ','.join([str(param.text) for param in params])
453        API = cmdinfo.elem.attrib.get('name').replace('vk','pTable->',1)
454        self.appendSection('command', '    ' + assignresult + API + '(' + paramstext + ');')
455        self.appendSection('command', '    if (threadChecks) {')
456        self.appendSection('command', "    "+"\n    ".join(str(finishthreadsafety).rstrip().split("\n")))
457        self.appendSection('command', '    } else {')
458        self.appendSection('command', '        finishMultiThread();')
459        self.appendSection('command', '    }')
460        # Return result variable, if any.
461        if (resulttype != None):
462            self.appendSection('command', '    return result;')
463        self.appendSection('command', '}')
464    #
465    # override makeProtoName to drop the "vk" prefix
466    def makeProtoName(self, name, tail):
467        return self.genOpts.apientry + name[2:] + tail
468