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