blob: 33857842243fdacb1461e4714bfe80b6e6af9fb4 [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:
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600137 if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail:
138 ispointer = True
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600139 return ispointer
140 def makeThreadUseBlock(self, cmd, functionprefix):
141 """Generate C function pointer typedef for <command> Element"""
142 paramdecl = ''
Mark Lobodzinski9c147802017-02-10 08:34:54 -0700143 # TODO: We should generate these lists
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600144 thread_check_dispatchable_objects = [
145 "VkCommandBuffer",
146 "VkDevice",
147 "VkInstance",
148 "VkQueue",
149 ]
150 thread_check_nondispatchable_objects = [
151 "VkBuffer",
152 "VkBufferView",
153 "VkCommandPool",
154 "VkDescriptorPool",
155 "VkDescriptorSetLayout",
156 "VkDeviceMemory",
157 "VkEvent",
158 "VkFence",
159 "VkFramebuffer",
160 "VkImage",
161 "VkImageView",
162 "VkPipeline",
163 "VkPipelineCache",
164 "VkPipelineLayout",
165 "VkQueryPool",
166 "VkRenderPass",
167 "VkSampler",
168 "VkSemaphore",
169 "VkShaderModule",
Mark Lobodzinski9c147802017-02-10 08:34:54 -0700170 "VkObjectTableNVX",
171 "VkIndirectCommandsLayoutNVX",
172 "VkDisplayKHR",
173 "VkDisplayModeKHR",
174 "VkSurfaceKHR",
175 "VkSwapchainKHR",
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600176 ]
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("::", "->")
Mark Lobodzinski9c147802017-02-10 08:34:54 -0700217 member = str(member).replace(".", "->")
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600218 paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + member + ');\n'
219 else:
220 paramtype = param.find('type')
221 if paramtype is not None:
222 paramtype = paramtype.text
223 else:
224 paramtype = 'None'
225 if paramtype in thread_check_dispatchable_objects or paramtype in thread_check_nondispatchable_objects:
226 if self.paramIsArray(param) and ('pPipelines' != paramname.text):
Mark Lobodzinski9c147802017-02-10 08:34:54 -0700227 # Add pointer dereference for array counts that are pointer values
228 dereference = ''
229 for candidate in params:
230 if param.attrib.get('len') == candidate.find('name').text:
231 if self.paramIsPointer(candidate):
232 dereference = '*'
233 paramdecl += ' for (uint32_t index = 0; index < ' + dereference + param.attrib.get('len') + '; index++) {\n'
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600234 paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + '[index]);\n'
235 paramdecl += ' }\n'
236 elif not self.paramIsPointer(param):
237 # Pointer params are often being created.
238 # They are not being read from.
239 paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + ');\n'
240 explicitexternsyncparams = cmd.findall("param[@externsync]")
241 if (explicitexternsyncparams is not None):
242 for param in explicitexternsyncparams:
243 externsyncattrib = param.attrib.get('externsync')
244 paramname = param.find('name')
245 paramdecl += ' // Host access to '
246 if externsyncattrib == 'true':
247 if self.paramIsArray(param):
248 paramdecl += 'each member of ' + paramname.text
249 elif self.paramIsPointer(param):
250 paramdecl += 'the object referenced by ' + paramname.text
251 else:
252 paramdecl += paramname.text
253 else:
254 paramdecl += externsyncattrib
255 paramdecl += ' must be externally synchronized\n'
256
257 # Find and add any "implicit" parameters that are thread unsafe
258 implicitexternsyncparams = cmd.find('implicitexternsyncparams')
259 if (implicitexternsyncparams is not None):
260 for elem in implicitexternsyncparams:
261 paramdecl += ' // '
262 paramdecl += elem.text
263 paramdecl += ' must be externally synchronized between host accesses\n'
264
265 if (paramdecl == ''):
266 return None
267 else:
268 return paramdecl
269 def beginFile(self, genOpts):
270 OutputGenerator.beginFile(self, genOpts)
271 # C-specific
272 #
273 # Multiple inclusion protection & C++ namespace.
274 if (genOpts.protectFile and self.genOpts.filename):
275 headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
276 write('#ifndef', headerSym, file=self.outFile)
277 write('#define', headerSym, '1', file=self.outFile)
278 self.newline()
279 write('namespace threading {', file=self.outFile)
280 self.newline()
281 #
282 # User-supplied prefix text, if any (list of strings)
283 if (genOpts.prefixText):
284 for s in genOpts.prefixText:
285 write(s, file=self.outFile)
286 def endFile(self):
287 # C-specific
288 # Finish C++ namespace and multiple inclusion protection
289 self.newline()
290 # record intercepted procedures
291 write('// intercepts', file=self.outFile)
292 write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile)
293 write('\n'.join(self.intercepts), file=self.outFile)
294 write('};\n', file=self.outFile)
295 self.newline()
296 write('} // namespace threading', file=self.outFile)
297 if (self.genOpts.protectFile and self.genOpts.filename):
298 self.newline()
299 write('#endif', file=self.outFile)
300 # Finish processing in superclass
301 OutputGenerator.endFile(self)
302 def beginFeature(self, interface, emit):
303 #write('// starting beginFeature', file=self.outFile)
304 # Start processing in superclass
305 OutputGenerator.beginFeature(self, interface, emit)
306 # C-specific
307 # Accumulate includes, defines, types, enums, function pointer typedefs,
308 # end function prototypes separately for this feature. They're only
309 # printed in endFeature().
310 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
311 #write('// ending beginFeature', file=self.outFile)
312 def endFeature(self):
313 # C-specific
314 # Actually write the interface to the output file.
315 #write('// starting endFeature', file=self.outFile)
316 if (self.emit):
317 self.newline()
318 if (self.genOpts.protectFeature):
319 write('#ifndef', self.featureName, file=self.outFile)
320 # If type declarations are needed by other features based on
321 # this one, it may be necessary to suppress the ExtraProtect,
322 # or move it below the 'for section...' loop.
323 #write('// endFeature looking at self.featureExtraProtect', file=self.outFile)
324 if (self.featureExtraProtect != None):
325 write('#ifdef', self.featureExtraProtect, file=self.outFile)
326 #write('#define', self.featureName, '1', file=self.outFile)
327 for section in self.TYPE_SECTIONS:
328 #write('// endFeature writing section'+section, file=self.outFile)
329 contents = self.sections[section]
330 if contents:
331 write('\n'.join(contents), file=self.outFile)
332 self.newline()
333 #write('// endFeature looking at self.sections[command]', file=self.outFile)
334 if (self.sections['command']):
Jamie Madill24aa9742016-12-13 17:02:57 -0500335 write('\n'.join(self.sections['command']), end=u'', file=self.outFile)
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600336 self.newline()
337 if (self.featureExtraProtect != None):
338 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
339 if (self.genOpts.protectFeature):
340 write('#endif /*', self.featureName, '*/', file=self.outFile)
341 # Finish processing in superclass
342 OutputGenerator.endFeature(self)
343 #write('// ending endFeature', file=self.outFile)
344 #
345 # Append a definition to the specified section
346 def appendSection(self, section, text):
347 # self.sections[section].append('SECTION: ' + section + '\n')
348 self.sections[section].append(text)
349 #
350 # Type generation
351 def genType(self, typeinfo, name):
352 pass
353 #
354 # Struct (e.g. C "struct" type) generation.
355 # This is a special case of the <type> tag where the contents are
356 # interpreted as a set of <member> tags instead of freeform C
357 # C type declarations. The <member> tags are just like <param>
358 # tags - they are a declaration of a struct or union member.
359 # Only simple member declarations are supported (no nested
360 # structs etc.)
361 def genStruct(self, typeinfo, typeName):
362 OutputGenerator.genStruct(self, typeinfo, typeName)
363 body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
364 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
365 for member in typeinfo.elem.findall('.//member'):
366 body += self.makeCParamDecl(member, self.genOpts.alignFuncParam)
367 body += ';\n'
368 body += '} ' + typeName + ';\n'
369 self.appendSection('struct', body)
370 #
371 # Group (e.g. C "enum" type) generation.
372 # These are concatenated together with other types.
373 def genGroup(self, groupinfo, groupName):
374 pass
375 # Enumerant generation
376 # <enum> tags may specify their values in several ways, but are usually
377 # just integers.
378 def genEnum(self, enuminfo, name):
379 pass
380 #
381 # Command generation
382 def genCmd(self, cmdinfo, name):
383 # Commands shadowed by interface functions and are not implemented
384 interface_functions = [
385 'vkEnumerateInstanceLayerProperties',
386 'vkEnumerateInstanceExtensionProperties',
387 'vkEnumerateDeviceLayerProperties',
388 ]
389 if name in interface_functions:
390 return
391 special_functions = [
392 'vkGetDeviceProcAddr',
393 'vkGetInstanceProcAddr',
394 'vkCreateDevice',
395 'vkDestroyDevice',
396 'vkCreateInstance',
397 'vkDestroyInstance',
398 'vkAllocateCommandBuffers',
399 'vkFreeCommandBuffers',
400 'vkCreateDebugReportCallbackEXT',
401 'vkDestroyDebugReportCallbackEXT',
402 ]
403 if name in special_functions:
404 decls = self.makeCDecls(cmdinfo.elem)
405 self.appendSection('command', '')
406 self.appendSection('command', '// declare only')
407 self.appendSection('command', decls[0])
408 self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name[2:]) ]
409 return
Mark Lobodzinski9c147802017-02-10 08:34:54 -0700410 if "QueuePresentKHR" in name or ("DebugMarker" in name and "EXT" in name):
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600411 self.appendSection('command', '// TODO - not wrapping EXT function ' + name)
412 return
413 # Determine first if this function needs to be intercepted
414 startthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'start')
415 if startthreadsafety is None:
416 return
417 finishthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'finish')
418 # record that the function will be intercepted
419 if (self.featureExtraProtect != None):
420 self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ]
421 self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name[2:]) ]
422 if (self.featureExtraProtect != None):
423 self.intercepts += [ '#endif' ]
424
425 OutputGenerator.genCmd(self, cmdinfo, name)
426 #
427 decls = self.makeCDecls(cmdinfo.elem)
428 self.appendSection('command', '')
429 self.appendSection('command', decls[0][:-1])
430 self.appendSection('command', '{')
431 # setup common to call wrappers
432 # first parameter is always dispatchable
433 dispatchable_type = cmdinfo.elem.find('param/type').text
434 dispatchable_name = cmdinfo.elem.find('param/name').text
435 self.appendSection('command', ' dispatch_key key = get_dispatch_key('+dispatchable_name+');')
Tobin Ehlis8d6acde2017-02-08 07:40:40 -0700436 self.appendSection('command', ' layer_data *my_data = GetLayerDataPtr(key, layer_data_map);')
Mark Lobodzinskiff910992016-10-11 14:29:52 -0600437 if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]:
438 self.appendSection('command', ' VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table;')
439 else:
440 self.appendSection('command', ' VkLayerDispatchTable *pTable = my_data->device_dispatch_table;')
441 # Declare result variable, if any.
442 resulttype = cmdinfo.elem.find('proto/type')
443 if (resulttype != None and resulttype.text == 'void'):
444 resulttype = None
445 if (resulttype != None):
446 self.appendSection('command', ' ' + resulttype.text + ' result;')
447 assignresult = 'result = '
448 else:
449 assignresult = ''
450
451 self.appendSection('command', ' bool threadChecks = startMultiThread();')
452 self.appendSection('command', ' if (threadChecks) {')
453 self.appendSection('command', " "+"\n ".join(str(startthreadsafety).rstrip().split("\n")))
454 self.appendSection('command', ' }')
455 params = cmdinfo.elem.findall('param/name')
456 paramstext = ','.join([str(param.text) for param in params])
457 API = cmdinfo.elem.attrib.get('name').replace('vk','pTable->',1)
458 self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');')
459 self.appendSection('command', ' if (threadChecks) {')
460 self.appendSection('command', " "+"\n ".join(str(finishthreadsafety).rstrip().split("\n")))
461 self.appendSection('command', ' } else {')
462 self.appendSection('command', ' finishMultiThread();')
463 self.appendSection('command', ' }')
464 # Return result variable, if any.
465 if (resulttype != None):
466 self.appendSection('command', ' return result;')
467 self.appendSection('command', '}')
468 #
469 # override makeProtoName to drop the "vk" prefix
470 def makeProtoName(self, name, tail):
471 return self.genOpts.apientry + name[2:] + tail