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 (at] google.com> 21 22 import os,re,sys 23 from 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 58 class 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) 114 class 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