Corentin Wallez 0c38e92187 Split off the reusable part of the code generator
This is in preparation for using it to generate code for replacing glad
with our own OpenGL function pointer loading code.

BUG=dawn:165

Change-Id: Ic3e774ab207e85a1491f299ad06131c8095416ae
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/7781
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
2019-06-07 08:59:17 +00:00

348 lines
14 KiB
Python

#!/usr/bin/env python2
# Copyright 2017 The Dawn Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json, os, sys
from collections import namedtuple
import common
from common import Name
from generator_lib import Generator, run_generator, FileRender
import wire_cmd
############################################################
# PARSE
############################################################
def is_native_method(method):
return method.return_type.category == "natively defined" or \
any([arg.type.category == "natively defined" for arg in method.arguments])
def link_object(obj, types):
def make_method(json_data):
arguments = common.linked_record_members(json_data.get('args', []), types)
return common.Method(Name(json_data['name']), types[json_data.get('returns', 'void')], arguments)
methods = [make_method(m) for m in obj.json_data.get('methods', [])]
obj.methods = [method for method in methods if not is_native_method(method)]
obj.native_methods = [method for method in methods if is_native_method(method)]
def link_structure(struct, types):
struct.members = common.linked_record_members(struct.json_data['members'], types)
# Sort structures so that if struct A has struct B as a member, then B is listed before A
# This is a form of topological sort where we try to keep the order reasonably similar to the
# original order (though th sort isn't technically stable).
# It works by computing for each struct type what is the depth of its DAG of dependents, then
# resorting based on that depth using Python's stable sort. This makes a toposort because if
# A depends on B then its depth will be bigger than B's. It is also nice because all nodes
# with the same depth are kept in the input order.
def topo_sort_structure(structs):
for struct in structs:
struct.visited = False
struct.subdag_depth = 0
def compute_depth(struct):
if struct.visited:
return struct.subdag_depth
max_dependent_depth = 0
for member in struct.members:
if member.type.category == 'structure':
max_dependent_depth = max(max_dependent_depth, compute_depth(member.type) + 1)
struct.subdag_depth = max_dependent_depth
struct.visited = True
return struct.subdag_depth
for struct in structs:
compute_depth(struct)
result = sorted(structs, key=lambda struct: struct.subdag_depth)
for struct in structs:
del struct.visited
del struct.subdag_depth
return result
def parse_json(json):
category_to_parser = {
'bitmask': common.BitmaskType,
'enum': common.EnumType,
'native': common.NativeType,
'natively defined': common.NativelyDefined,
'object': common.ObjectType,
'structure': common.StructureType,
}
types = {}
by_category = {}
for name in category_to_parser.keys():
by_category[name] = []
for (name, json_data) in json.items():
if name[0] == '_':
continue
category = json_data['category']
parsed = category_to_parser[category](name, json_data)
by_category[category].append(parsed)
types[name] = parsed
for obj in by_category['object']:
link_object(obj, types)
for struct in by_category['structure']:
link_structure(struct, types)
for category in by_category.keys():
by_category[category] = sorted(by_category[category], key=lambda typ: typ.name.canonical_case())
by_category['structure'] = topo_sort_structure(by_category['structure'])
for struct in by_category['structure']:
struct.update_metadata()
return {
'types': types,
'by_category': by_category
}
#############################################################
# Generator
#############################################################
def as_varName(*names):
return names[0].camelCase() + ''.join([name.CamelCase() for name in names[1:]])
def as_cType(name):
if name.native:
return name.concatcase()
else:
return 'Dawn' + name.CamelCase()
def as_cppType(name):
if name.native:
return name.concatcase()
else:
return name.CamelCase()
def convert_cType_to_cppType(typ, annotation, arg, indent=0):
if typ.category == 'native':
return arg
if annotation == 'value':
if typ.category == 'object':
return '{}::Acquire({})'.format(as_cppType(typ.name), arg)
elif typ.category == 'structure':
converted_members = [
convert_cType_to_cppType(
member.type, member.annotation,
'{}.{}'.format(arg, as_varName(member.name)),
indent + 1)
for member in typ.members]
converted_members = [(' ' * 4) + m for m in converted_members ]
converted_members = ',\n'.join(converted_members)
return as_cppType(typ.name) + ' {\n' + converted_members + '\n}'
else:
return 'static_cast<{}>({})'.format(as_cppType(typ.name), arg)
else:
return 'reinterpret_cast<{} {}>({})'.format(as_cppType(typ.name), annotation, arg)
def decorate(name, typ, arg):
if arg.annotation == 'value':
return typ + ' ' + name
elif arg.annotation == '*':
return typ + ' * ' + name
elif arg.annotation == 'const*':
return typ + ' const * ' + name
elif arg.annotation == 'const*const*':
return 'const ' + typ + '* const * ' + name
else:
assert(False)
def annotated(typ, arg):
name = as_varName(arg.name)
return decorate(name, typ, arg)
def as_cEnum(type_name, value_name):
assert(not type_name.native and not value_name.native)
return 'DAWN' + '_' + type_name.SNAKE_CASE() + '_' + value_name.SNAKE_CASE()
def as_cppEnum(value_name):
assert(not value_name.native)
if value_name.concatcase()[0].isdigit():
return "e" + value_name.CamelCase()
return value_name.CamelCase()
def as_cMethod(type_name, method_name):
assert(not type_name.native and not method_name.native)
return 'dawn' + type_name.CamelCase() + method_name.CamelCase()
def as_MethodSuffix(type_name, method_name):
assert(not type_name.native and not method_name.native)
return type_name.CamelCase() + method_name.CamelCase()
def as_cProc(type_name, method_name):
assert(not type_name.native and not method_name.native)
return 'Dawn' + 'Proc' + type_name.CamelCase() + method_name.CamelCase()
def as_frontendType(typ):
if typ.category == 'object':
return typ.name.CamelCase() + 'Base*'
elif typ.category in ['bitmask', 'enum']:
return 'dawn::' + typ.name.CamelCase()
elif typ.category == 'structure':
return as_cppType(typ.name)
else:
return as_cType(typ.name)
def cpp_native_methods(types, typ):
return typ.methods + typ.native_methods
def c_native_methods(types, typ):
return cpp_native_methods(types, typ) + [
common.Method(Name('reference'), types['void'], []),
common.Method(Name('release'), types['void'], []),
]
def js_native_methods(types, typ):
return cpp_native_methods(types, typ)
def debug(text):
print(text)
def do_assert(expr):
assert expr
return ''
class MultiGeneratorFromDawnJSON(Generator):
def get_description(self):
return 'Generates code for various target from Dawn.json.'
def add_commandline_arguments(self, parser):
allowed_targets = ['dawn_headers', 'libdawn', 'mock_dawn', 'dawn_wire', "dawn_native_utils"]
parser.add_argument('--dawn-json', required=True, type=str, help ='The DAWN JSON definition to use.')
parser.add_argument('--wire-json', default=None, type=str, help='The DAWN WIRE JSON definition to use.')
parser.add_argument('-T', '--targets', required=True, type=str, help='Comma-separated subset of targets to output. Available targets: ' + ', '.join(allowed_targets))
def get_file_renders(self, args):
with open(args.dawn_json) as f:
loaded_json = json.loads(f.read())
api_params = parse_json(loaded_json)
targets = args.targets.split(',')
wire_json = None
if args.wire_json:
with open(args.wire_json) as f:
wire_json = json.loads(f.read())
base_params = {
'enumerate': enumerate,
'format': format,
'len': len,
'debug': debug,
'assert': do_assert,
'Name': lambda name: Name(name),
'as_annotated_cType': lambda arg: annotated(as_cType(arg.type.name), arg),
'as_annotated_cppType': lambda arg: annotated(as_cppType(arg.type.name), arg),
'as_cEnum': as_cEnum,
'as_cppEnum': as_cppEnum,
'as_cMethod': as_cMethod,
'as_MethodSuffix': as_MethodSuffix,
'as_cProc': as_cProc,
'as_cType': as_cType,
'as_cppType': as_cppType,
'convert_cType_to_cppType': convert_cType_to_cppType,
'as_varName': as_varName,
'decorate': decorate,
}
renders = []
c_params = {'native_methods': lambda typ: c_native_methods(api_params['types'], typ)}
cpp_params = {'native_methods': lambda typ: cpp_native_methods(api_params['types'], typ)}
if 'dawn_headers' in targets:
renders.append(FileRender('api.h', 'dawn/dawn.h', [base_params, api_params, c_params]))
renders.append(FileRender('apicpp.h', 'dawn/dawncpp.h', [base_params, api_params, cpp_params]))
if 'libdawn' in targets:
additional_params = {'native_methods': lambda typ: cpp_native_methods(api_params['types'], typ)}
renders.append(FileRender('api.c', 'dawn/dawn.c', [base_params, api_params, c_params]))
renders.append(FileRender('apicpp.cpp', 'dawn/dawncpp.cpp', [base_params, api_params, cpp_params]))
if 'mock_dawn' in targets:
renders.append(FileRender('mock_api.h', 'mock/mock_dawn.h', [base_params, api_params, c_params]))
renders.append(FileRender('mock_api.cpp', 'mock/mock_dawn.cpp', [base_params, api_params, c_params]))
if 'dawn_native_utils' in targets:
frontend_params = [
base_params,
api_params,
c_params,
{
'as_frontendType': lambda typ: as_frontendType(typ), # TODO as_frontendType and friends take a Type and not a Name :(
'as_annotated_frontendType': lambda arg: annotated(as_frontendType(arg.type), arg)
}
]
renders.append(FileRender('dawn_native/ValidationUtils.h', 'dawn_native/ValidationUtils_autogen.h', frontend_params))
renders.append(FileRender('dawn_native/ValidationUtils.cpp', 'dawn_native/ValidationUtils_autogen.cpp', frontend_params))
renders.append(FileRender('dawn_native/api_structs.h', 'dawn_native/dawn_structs_autogen.h', frontend_params))
renders.append(FileRender('dawn_native/api_structs.cpp', 'dawn_native/dawn_structs_autogen.cpp', frontend_params))
renders.append(FileRender('dawn_native/ProcTable.cpp', 'dawn_native/ProcTable.cpp', frontend_params))
if 'dawn_wire' in targets:
additional_params = wire_cmd.compute_wire_params(api_params, wire_json)
wire_params = [
base_params,
api_params,
c_params,
{
'as_wireType': lambda typ: typ.name.CamelCase() + '*' if typ.category == 'object' else as_cppType(typ.name)
},
additional_params
]
renders.append(FileRender('dawn_wire/WireCmd.h', 'dawn_wire/WireCmd_autogen.h', wire_params))
renders.append(FileRender('dawn_wire/WireCmd.cpp', 'dawn_wire/WireCmd_autogen.cpp', wire_params))
renders.append(FileRender('dawn_wire/client/ApiObjects.h', 'dawn_wire/client/ApiObjects_autogen.h', wire_params))
renders.append(FileRender('dawn_wire/client/ApiProcs.cpp', 'dawn_wire/client/ApiProcs_autogen.cpp', wire_params))
renders.append(FileRender('dawn_wire/client/ApiProcs.h', 'dawn_wire/client/ApiProcs_autogen.h', wire_params))
renders.append(FileRender('dawn_wire/client/ClientBase.h', 'dawn_wire/client/ClientBase_autogen.h', wire_params))
renders.append(FileRender('dawn_wire/client/ClientHandlers.cpp', 'dawn_wire/client/ClientHandlers_autogen.cpp', wire_params))
renders.append(FileRender('dawn_wire/client/ClientPrototypes.inc', 'dawn_wire/client/ClientPrototypes_autogen.inc', wire_params))
renders.append(FileRender('dawn_wire/server/ServerBase.h', 'dawn_wire/server/ServerBase_autogen.h', wire_params))
renders.append(FileRender('dawn_wire/server/ServerDoers.cpp', 'dawn_wire/server/ServerDoers_autogen.cpp', wire_params))
renders.append(FileRender('dawn_wire/server/ServerHandlers.cpp', 'dawn_wire/server/ServerHandlers_autogen.cpp', wire_params))
renders.append(FileRender('dawn_wire/server/ServerPrototypes.inc', 'dawn_wire/server/ServerPrototypes_autogen.inc', wire_params))
return renders
def get_dependencies(self, args):
deps = [os.path.abspath(args.dawn_json)]
if args.wire_json != None:
deps += [os.path.abspath(args.wire_json)]
return deps
if __name__ == '__main__':
sys.exit(run_generator(MultiGeneratorFromDawnJSON()))