blob: 0d0df12a687e36264ecc83a0d711bce872f5242b [file] [log] [blame]
Mark Lobodzinskiff910992016-10-11 14:29:52 -06001#!/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