1#!/usr/bin/env python2 2# Copyright 2017 The Dawn Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import json, os, sys 17from collections import namedtuple 18 19from generator_lib import Generator, run_generator, FileRender 20 21############################################################ 22# OBJECT MODEL 23############################################################ 24 25class Name: 26 def __init__(self, name, native=False): 27 self.native = native 28 if native: 29 self.chunks = [name] 30 else: 31 self.chunks = name.split(' ') 32 33 def CamelChunk(self, chunk): 34 return chunk[0].upper() + chunk[1:] 35 36 def canonical_case(self): 37 return (' '.join(self.chunks)).lower() 38 39 def concatcase(self): 40 return ''.join(self.chunks) 41 42 def camelCase(self): 43 return self.chunks[0] + ''.join([self.CamelChunk(chunk) for chunk in self.chunks[1:]]) 44 45 def CamelCase(self): 46 return ''.join([self.CamelChunk(chunk) for chunk in self.chunks]) 47 48 def SNAKE_CASE(self): 49 return '_'.join([chunk.upper() for chunk in self.chunks]) 50 51 def snake_case(self): 52 return '_'.join(self.chunks) 53 54 def js_enum_case(self): 55 result = self.chunks[0].lower() 56 for chunk in self.chunks[1:]: 57 if not result[-1].isdigit(): 58 result += '-' 59 result += chunk.lower() 60 return result 61 62def concat_names(*names): 63 return ' '.join([name.canonical_case() for name in names]) 64 65class Type: 66 def __init__(self, name, json_data, native=False): 67 self.json_data = json_data 68 self.dict_name = name 69 self.name = Name(name, native=native) 70 self.category = json_data['category'] 71 self.javascript = self.json_data.get('javascript', True) 72 73EnumValue = namedtuple('EnumValue', ['name', 'value', 'valid', 'jsrepr']) 74class EnumType(Type): 75 def __init__(self, name, json_data): 76 Type.__init__(self, name, json_data) 77 78 self.values = [] 79 self.contiguousFromZero = True 80 lastValue = -1 81 for m in self.json_data['values']: 82 value = m['value'] 83 if value != lastValue + 1: 84 self.contiguousFromZero = False 85 lastValue = value 86 self.values.append(EnumValue( 87 Name(m['name']), 88 value, 89 m.get('valid', True), 90 m.get('jsrepr', None))) 91 92 # Assert that all values are unique in enums 93 all_values = set() 94 for value in self.values: 95 if value.value in all_values: 96 raise Exception("Duplicate value {} in enum {}".format(value.value, name)) 97 all_values.add(value.value) 98 99BitmaskValue = namedtuple('BitmaskValue', ['name', 'value']) 100class BitmaskType(Type): 101 def __init__(self, name, json_data): 102 Type.__init__(self, name, json_data) 103 self.values = [BitmaskValue(Name(m['name']), m['value']) for m in self.json_data['values']] 104 self.full_mask = 0 105 for value in self.values: 106 self.full_mask = self.full_mask | value.value 107 108class CallbackType(Type): 109 def __init__(self, name, json_data): 110 Type.__init__(self, name, json_data) 111 self.arguments = [] 112 113class NativeType(Type): 114 def __init__(self, name, json_data): 115 Type.__init__(self, name, json_data, native=True) 116 117# Methods and structures are both "records", so record members correspond to 118# method arguments or structure members. 119class RecordMember: 120 def __init__(self, name, typ, annotation, optional=False, 121 is_return_value=False, default_value=None, 122 skip_serialize=False): 123 self.name = name 124 self.type = typ 125 self.annotation = annotation 126 self.length = None 127 self.optional = optional 128 self.is_return_value = is_return_value 129 self.handle_type = None 130 self.default_value = default_value 131 self.skip_serialize = skip_serialize 132 133 def set_handle_type(self, handle_type): 134 assert self.type.dict_name == "ObjectHandle" 135 self.handle_type = handle_type 136 137Method = namedtuple('Method', ['name', 'return_type', 'arguments']) 138class ObjectType(Type): 139 def __init__(self, name, json_data): 140 Type.__init__(self, name, json_data) 141 self.methods = [] 142 self.built_type = None 143 144class Record: 145 def __init__(self, name): 146 self.name = Name(name) 147 self.members = [] 148 self.has_dawn_object = False 149 150 def update_metadata(self): 151 def has_dawn_object(member): 152 if isinstance(member.type, ObjectType): 153 return True 154 elif isinstance(member.type, StructureType): 155 return member.type.has_dawn_object 156 else: 157 return False 158 159 self.has_dawn_object = any(has_dawn_object(member) for member in self.members) 160 161class StructureType(Record, Type): 162 def __init__(self, name, json_data): 163 Record.__init__(self, name) 164 Type.__init__(self, name, json_data) 165 self.chained = json_data.get("chained", False) 166 self.extensible = json_data.get("extensible", False) 167 # Chained structs inherit from wgpu::ChainedStruct which has nextInChain so setting 168 # both extensible and chained would result in two nextInChain members. 169 assert(not (self.extensible and self.chained)) 170 171class Command(Record): 172 def __init__(self, name, members=None): 173 Record.__init__(self, name) 174 self.members = members or [] 175 self.derived_object = None 176 self.derived_method = None 177 178def linked_record_members(json_data, types): 179 members = [] 180 members_by_name = {} 181 for m in json_data: 182 member = RecordMember(Name(m['name']), types[m['type']], 183 m.get('annotation', 'value'), 184 optional=m.get('optional', False), 185 is_return_value=m.get('is_return_value', False), 186 default_value=m.get('default', None), 187 skip_serialize=m.get('skip_serialize', False)) 188 handle_type = m.get('handle_type') 189 if handle_type: 190 member.set_handle_type(types[handle_type]) 191 members.append(member) 192 members_by_name[member.name.canonical_case()] = member 193 194 for (member, m) in zip(members, json_data): 195 if member.annotation != 'value': 196 if not 'length' in m: 197 if member.type.category != 'object': 198 member.length = "constant" 199 member.constant_length = 1 200 else: 201 assert(False) 202 elif m['length'] == 'strlen': 203 member.length = 'strlen' 204 else: 205 member.length = members_by_name[m['length']] 206 207 return members 208 209############################################################ 210# PARSE 211############################################################ 212 213def link_object(obj, types): 214 def make_method(json_data): 215 arguments = linked_record_members(json_data.get('args', []), types) 216 return Method(Name(json_data['name']), types[json_data.get('returns', 'void')], arguments) 217 218 obj.methods = [make_method(m) for m in obj.json_data.get('methods', [])] 219 obj.methods.sort(key=lambda method: method.name.canonical_case()) 220 221def link_structure(struct, types): 222 struct.members = linked_record_members(struct.json_data['members'], types) 223 224def link_callback(callback, types): 225 callback.arguments = linked_record_members(callback.json_data['args'], types) 226 227# Sort structures so that if struct A has struct B as a member, then B is listed before A 228# This is a form of topological sort where we try to keep the order reasonably similar to the 229# original order (though th sort isn't technically stable). 230# It works by computing for each struct type what is the depth of its DAG of dependents, then 231# resorting based on that depth using Python's stable sort. This makes a toposort because if 232# A depends on B then its depth will be bigger than B's. It is also nice because all nodes 233# with the same depth are kept in the input order. 234def topo_sort_structure(structs): 235 for struct in structs: 236 struct.visited = False 237 struct.subdag_depth = 0 238 239 def compute_depth(struct): 240 if struct.visited: 241 return struct.subdag_depth 242 243 max_dependent_depth = 0 244 for member in struct.members: 245 if member.type.category == 'structure': 246 max_dependent_depth = max(max_dependent_depth, compute_depth(member.type) + 1) 247 248 struct.subdag_depth = max_dependent_depth 249 struct.visited = True 250 return struct.subdag_depth 251 252 for struct in structs: 253 compute_depth(struct) 254 255 result = sorted(structs, key=lambda struct: struct.subdag_depth) 256 257 for struct in structs: 258 del struct.visited 259 del struct.subdag_depth 260 261 return result 262 263def parse_json(json): 264 category_to_parser = { 265 'bitmask': BitmaskType, 266 'enum': EnumType, 267 'native': NativeType, 268 'callback': CallbackType, 269 'object': ObjectType, 270 'structure': StructureType, 271 } 272 273 types = {} 274 275 by_category = {} 276 for name in category_to_parser.keys(): 277 by_category[name] = [] 278 279 for (name, json_data) in json.items(): 280 if name[0] == '_': 281 continue 282 category = json_data['category'] 283 parsed = category_to_parser[category](name, json_data) 284 by_category[category].append(parsed) 285 types[name] = parsed 286 287 for obj in by_category['object']: 288 link_object(obj, types) 289 290 for struct in by_category['structure']: 291 link_structure(struct, types) 292 293 for callback in by_category['callback']: 294 link_callback(callback, types) 295 296 for category in by_category.keys(): 297 by_category[category] = sorted(by_category[category], key=lambda typ: typ.name.canonical_case()) 298 299 by_category['structure'] = topo_sort_structure(by_category['structure']) 300 301 for struct in by_category['structure']: 302 struct.update_metadata() 303 304 return { 305 'types': types, 306 'by_category': by_category 307 } 308 309############################################################ 310# WIRE STUFF 311############################################################ 312 313# Create wire commands from api methods 314def compute_wire_params(api_params, wire_json): 315 wire_params = api_params.copy() 316 types = wire_params['types'] 317 318 commands = [] 319 return_commands = [] 320 321 wire_json['special items']['client_handwritten_commands'] += wire_json['special items']['client_side_commands'] 322 323 # Generate commands from object methods 324 for api_object in wire_params['by_category']['object']: 325 for method in api_object.methods: 326 command_name = concat_names(api_object.name, method.name) 327 command_suffix = Name(command_name).CamelCase() 328 329 # Only object return values or void are supported. Other methods must be handwritten. 330 if method.return_type.category != 'object' and method.return_type.name.canonical_case() != 'void': 331 assert(command_suffix in wire_json['special items']['client_handwritten_commands']) 332 continue 333 334 if command_suffix in wire_json['special items']['client_side_commands']: 335 continue 336 337 # Create object method commands by prepending "self" 338 members = [RecordMember(Name('self'), types[api_object.dict_name], 'value')] 339 members += method.arguments 340 341 # Client->Server commands that return an object return the result object handle 342 if method.return_type.category == 'object': 343 result = RecordMember(Name('result'), types['ObjectHandle'], 'value', is_return_value=True) 344 result.set_handle_type(method.return_type) 345 members.append(result) 346 347 command = Command(command_name, members) 348 command.derived_object = api_object 349 command.derived_method = method 350 commands.append(command) 351 352 for (name, json_data) in wire_json['commands'].items(): 353 commands.append(Command(name, linked_record_members(json_data, types))) 354 355 for (name, json_data) in wire_json['return commands'].items(): 356 return_commands.append(Command(name, linked_record_members(json_data, types))) 357 358 wire_params['cmd_records'] = { 359 'command': commands, 360 'return command': return_commands 361 } 362 363 for commands in wire_params['cmd_records'].values(): 364 for command in commands: 365 command.update_metadata() 366 commands.sort(key=lambda c: c.name.canonical_case()) 367 368 wire_params.update(wire_json.get('special items', {})) 369 370 return wire_params 371 372############################################################# 373# Generator 374############################################################# 375 376def as_varName(*names): 377 return names[0].camelCase() + ''.join([name.CamelCase() for name in names[1:]]) 378 379def as_cType(name): 380 if name.native: 381 return name.concatcase() 382 else: 383 return 'WGPU' + name.CamelCase() 384 385def as_cTypeDawn(name): 386 if name.native: 387 return name.concatcase() 388 else: 389 return 'Dawn' + name.CamelCase() 390 391def as_cTypeEnumSpecialCase(typ): 392 if typ.category == 'bitmask': 393 return as_cType(typ.name) + 'Flags' 394 return as_cType(typ.name) 395 396def as_cppType(name): 397 if name.native: 398 return name.concatcase() 399 else: 400 return name.CamelCase() 401 402def as_jsEnumValue(value): 403 if value.jsrepr: return value.jsrepr 404 return "'" + value.name.js_enum_case() + "'" 405 406def convert_cType_to_cppType(typ, annotation, arg, indent=0): 407 if typ.category == 'native': 408 return arg 409 if annotation == 'value': 410 if typ.category == 'object': 411 return '{}::Acquire({})'.format(as_cppType(typ.name), arg) 412 elif typ.category == 'structure': 413 converted_members = [ 414 convert_cType_to_cppType( 415 member.type, member.annotation, 416 '{}.{}'.format(arg, as_varName(member.name)), 417 indent + 1) 418 for member in typ.members] 419 420 converted_members = [(' ' * 4) + m for m in converted_members ] 421 converted_members = ',\n'.join(converted_members) 422 423 return as_cppType(typ.name) + ' {\n' + converted_members + '\n}' 424 else: 425 return 'static_cast<{}>({})'.format(as_cppType(typ.name), arg) 426 else: 427 return 'reinterpret_cast<{} {}>({})'.format(as_cppType(typ.name), annotation, arg) 428 429def decorate(name, typ, arg): 430 if arg.annotation == 'value': 431 return typ + ' ' + name 432 elif arg.annotation == '*': 433 return typ + ' * ' + name 434 elif arg.annotation == 'const*': 435 return typ + ' const * ' + name 436 elif arg.annotation == 'const*const*': 437 return 'const ' + typ + '* const * ' + name 438 else: 439 assert(False) 440 441def annotated(typ, arg): 442 name = as_varName(arg.name) 443 return decorate(name, typ, arg) 444 445def as_cEnum(type_name, value_name): 446 assert(not type_name.native and not value_name.native) 447 return 'WGPU' + type_name.CamelCase() + '_' + value_name.CamelCase() 448 449def as_cEnumDawn(type_name, value_name): 450 assert(not type_name.native and not value_name.native) 451 return 'DAWN' + '_' + type_name.SNAKE_CASE() + '_' + value_name.SNAKE_CASE() 452 453def as_cppEnum(value_name): 454 assert(not value_name.native) 455 if value_name.concatcase()[0].isdigit(): 456 return "e" + value_name.CamelCase() 457 return value_name.CamelCase() 458 459def as_cMethod(type_name, method_name): 460 assert(not type_name.native and not method_name.native) 461 return 'wgpu' + type_name.CamelCase() + method_name.CamelCase() 462 463def as_cMethodDawn(type_name, method_name): 464 assert(not type_name.native and not method_name.native) 465 return 'dawn' + type_name.CamelCase() + method_name.CamelCase() 466 467def as_MethodSuffix(type_name, method_name): 468 assert(not type_name.native and not method_name.native) 469 return type_name.CamelCase() + method_name.CamelCase() 470 471def as_cProc(type_name, method_name): 472 assert(not type_name.native and not method_name.native) 473 return 'WGPU' + 'Proc' + type_name.CamelCase() + method_name.CamelCase() 474 475def as_cProcDawn(type_name, method_name): 476 assert(not type_name.native and not method_name.native) 477 return 'Dawn' + 'Proc' + type_name.CamelCase() + method_name.CamelCase() 478 479def as_frontendType(typ): 480 if typ.category == 'object': 481 return typ.name.CamelCase() + 'Base*' 482 elif typ.category in ['bitmask', 'enum']: 483 return 'wgpu::' + typ.name.CamelCase() 484 elif typ.category == 'structure': 485 return as_cppType(typ.name) 486 else: 487 return as_cType(typ.name) 488 489def as_wireType(typ): 490 if typ.category == 'object': 491 return typ.name.CamelCase() + '*' 492 elif typ.category in ['bitmask', 'enum']: 493 return 'WGPU' + typ.name.CamelCase() 494 else: 495 return as_cppType(typ.name) 496 497def c_methods(types, typ): 498 return typ.methods + [ 499 Method(Name('reference'), types['void'], []), 500 Method(Name('release'), types['void'], []), 501 ] 502 503def get_c_methods_sorted_by_name(api_params): 504 unsorted = [(as_MethodSuffix(typ.name, method.name), typ, method) \ 505 for typ in api_params['by_category']['object'] \ 506 for method in c_methods(api_params['types'], typ) ] 507 return [(typ, method) for (_, typ, method) in sorted(unsorted)] 508 509def has_callback_arguments(method): 510 return any(arg.type.category == 'callback' for arg in method.arguments) 511 512class MultiGeneratorFromDawnJSON(Generator): 513 def get_description(self): 514 return 'Generates code for various target from Dawn.json.' 515 516 def add_commandline_arguments(self, parser): 517 allowed_targets = ['dawn_headers', 'dawncpp_headers', 'dawncpp', 'dawn_proc', 'mock_webgpu', 'dawn_wire', "dawn_native_utils"] 518 519 parser.add_argument('--dawn-json', required=True, type=str, help ='The DAWN JSON definition to use.') 520 parser.add_argument('--wire-json', default=None, type=str, help='The DAWN WIRE JSON definition to use.') 521 parser.add_argument('--targets', required=True, type=str, help='Comma-separated subset of targets to output. Available targets: ' + ', '.join(allowed_targets)) 522 523 def get_file_renders(self, args): 524 with open(args.dawn_json) as f: 525 loaded_json = json.loads(f.read()) 526 api_params = parse_json(loaded_json) 527 528 targets = args.targets.split(',') 529 530 wire_json = None 531 if args.wire_json: 532 with open(args.wire_json) as f: 533 wire_json = json.loads(f.read()) 534 535 base_params = { 536 'Name': lambda name: Name(name), 537 538 'as_annotated_cType': lambda arg: annotated(as_cTypeEnumSpecialCase(arg.type), arg), 539 'as_annotated_cppType': lambda arg: annotated(as_cppType(arg.type.name), arg), 540 'as_cEnum': as_cEnum, 541 'as_cEnumDawn': as_cEnumDawn, 542 'as_cppEnum': as_cppEnum, 543 'as_cMethod': as_cMethod, 544 'as_cMethodDawn': as_cMethodDawn, 545 'as_MethodSuffix': as_MethodSuffix, 546 'as_cProc': as_cProc, 547 'as_cProcDawn': as_cProcDawn, 548 'as_cType': as_cType, 549 'as_cTypeDawn': as_cTypeDawn, 550 'as_cppType': as_cppType, 551 'as_jsEnumValue': as_jsEnumValue, 552 'convert_cType_to_cppType': convert_cType_to_cppType, 553 'as_varName': as_varName, 554 'decorate': decorate, 555 'c_methods': lambda typ: c_methods(api_params['types'], typ), 556 'c_methods_sorted_by_name': get_c_methods_sorted_by_name(api_params), 557 } 558 559 renders = [] 560 561 if 'dawn_headers' in targets: 562 renders.append(FileRender('webgpu.h', 'src/include/dawn/webgpu.h', [base_params, api_params])) 563 renders.append(FileRender('dawn_proc_table.h', 'src/include/dawn/dawn_proc_table.h', [base_params, api_params])) 564 565 if 'dawncpp_headers' in targets: 566 renders.append(FileRender('webgpu_cpp.h', 'src/include/dawn/webgpu_cpp.h', [base_params, api_params])) 567 568 if 'dawn_proc' in targets: 569 renders.append(FileRender('dawn_proc.c', 'src/dawn/dawn_proc.c', [base_params, api_params])) 570 571 if 'dawncpp' in targets: 572 renders.append(FileRender('webgpu_cpp.cpp', 'src/dawn/webgpu_cpp.cpp', [base_params, api_params])) 573 574 if 'emscripten_bits' in targets: 575 renders.append(FileRender('webgpu_struct_info.json', 'src/dawn/webgpu_struct_info.json', [base_params, api_params])) 576 renders.append(FileRender('library_webgpu_enum_tables.js', 'src/dawn/library_webgpu_enum_tables.js', [base_params, api_params])) 577 578 if 'mock_webgpu' in targets: 579 mock_params = [ 580 base_params, 581 api_params, 582 { 583 'has_callback_arguments': has_callback_arguments 584 } 585 ] 586 renders.append(FileRender('mock_webgpu.h', 'src/dawn/mock_webgpu.h', mock_params)) 587 renders.append(FileRender('mock_webgpu.cpp', 'src/dawn/mock_webgpu.cpp', mock_params)) 588 589 if 'dawn_native_utils' in targets: 590 frontend_params = [ 591 base_params, 592 api_params, 593 { 594 'as_frontendType': lambda typ: as_frontendType(typ), # TODO as_frontendType and friends take a Type and not a Name :( 595 'as_annotated_frontendType': lambda arg: annotated(as_frontendType(arg.type), arg) 596 } 597 ] 598 599 renders.append(FileRender('dawn_native/ValidationUtils.h', 'src/dawn_native/ValidationUtils_autogen.h', frontend_params)) 600 renders.append(FileRender('dawn_native/ValidationUtils.cpp', 'src/dawn_native/ValidationUtils_autogen.cpp', frontend_params)) 601 renders.append(FileRender('dawn_native/wgpu_structs.h', 'src/dawn_native/wgpu_structs_autogen.h', frontend_params)) 602 renders.append(FileRender('dawn_native/wgpu_structs.cpp', 'src/dawn_native/wgpu_structs_autogen.cpp', frontend_params)) 603 renders.append(FileRender('dawn_native/ProcTable.cpp', 'src/dawn_native/ProcTable.cpp', frontend_params)) 604 605 if 'dawn_wire' in targets: 606 additional_params = compute_wire_params(api_params, wire_json) 607 608 wire_params = [ 609 base_params, 610 api_params, 611 { 612 'as_wireType': as_wireType, 613 'as_annotated_wireType': lambda arg: annotated(as_wireType(arg.type), arg), 614 }, 615 additional_params 616 ] 617 renders.append(FileRender('dawn_wire/WireCmd.h', 'src/dawn_wire/WireCmd_autogen.h', wire_params)) 618 renders.append(FileRender('dawn_wire/WireCmd.cpp', 'src/dawn_wire/WireCmd_autogen.cpp', wire_params)) 619 renders.append(FileRender('dawn_wire/client/ApiObjects.h', 'src/dawn_wire/client/ApiObjects_autogen.h', wire_params)) 620 renders.append(FileRender('dawn_wire/client/ApiProcs.cpp', 'src/dawn_wire/client/ApiProcs_autogen.cpp', wire_params)) 621 renders.append(FileRender('dawn_wire/client/ApiProcs.h', 'src/dawn_wire/client/ApiProcs_autogen.h', wire_params)) 622 renders.append(FileRender('dawn_wire/client/ClientBase.h', 'src/dawn_wire/client/ClientBase_autogen.h', wire_params)) 623 renders.append(FileRender('dawn_wire/client/ClientHandlers.cpp', 'src/dawn_wire/client/ClientHandlers_autogen.cpp', wire_params)) 624 renders.append(FileRender('dawn_wire/client/ClientPrototypes.inc', 'src/dawn_wire/client/ClientPrototypes_autogen.inc', wire_params)) 625 renders.append(FileRender('dawn_wire/server/ServerBase.h', 'src/dawn_wire/server/ServerBase_autogen.h', wire_params)) 626 renders.append(FileRender('dawn_wire/server/ServerDoers.cpp', 'src/dawn_wire/server/ServerDoers_autogen.cpp', wire_params)) 627 renders.append(FileRender('dawn_wire/server/ServerHandlers.cpp', 'src/dawn_wire/server/ServerHandlers_autogen.cpp', wire_params)) 628 renders.append(FileRender('dawn_wire/server/ServerPrototypes.inc', 'src/dawn_wire/server/ServerPrototypes_autogen.inc', wire_params)) 629 630 return renders 631 632 def get_dependencies(self, args): 633 deps = [os.path.abspath(args.dawn_json)] 634 if args.wire_json != None: 635 deps += [os.path.abspath(args.wire_json)] 636 return deps 637 638if __name__ == '__main__': 639 sys.exit(run_generator(MultiGeneratorFromDawnJSON())) 640