1233 lines
44 KiB
Python
1233 lines
44 KiB
Python
#!/usr/bin/env python3
|
|
# 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
|
|
|
|
from generator_lib import Generator, run_generator, FileRender
|
|
|
|
############################################################
|
|
# OBJECT MODEL
|
|
############################################################
|
|
|
|
|
|
class Metadata:
|
|
def __init__(self, metadata):
|
|
self.api = metadata['api']
|
|
self.namespace = metadata['namespace']
|
|
self.c_prefix = metadata.get('c_prefix', self.namespace.upper())
|
|
self.proc_table_prefix = metadata['proc_table_prefix']
|
|
self.impl_dir = metadata.get('impl_dir', '')
|
|
self.native_namespace = metadata['native_namespace']
|
|
self.copyright_year = metadata.get('copyright_year', None)
|
|
|
|
|
|
class Name:
|
|
def __init__(self, name, native=False):
|
|
self.native = native
|
|
self.name = name
|
|
if native:
|
|
self.chunks = [name]
|
|
else:
|
|
self.chunks = name.split(' ')
|
|
|
|
def get(self):
|
|
return self.name
|
|
|
|
def CamelChunk(self, chunk):
|
|
return chunk[0].upper() + chunk[1:]
|
|
|
|
def canonical_case(self):
|
|
return (' '.join(self.chunks)).lower()
|
|
|
|
def concatcase(self):
|
|
return ''.join(self.chunks)
|
|
|
|
def camelCase(self):
|
|
return self.chunks[0] + ''.join(
|
|
[self.CamelChunk(chunk) for chunk in self.chunks[1:]])
|
|
|
|
def CamelCase(self):
|
|
return ''.join([self.CamelChunk(chunk) for chunk in self.chunks])
|
|
|
|
def SNAKE_CASE(self):
|
|
return '_'.join([chunk.upper() for chunk in self.chunks])
|
|
|
|
def snake_case(self):
|
|
return '_'.join(self.chunks)
|
|
|
|
def namespace_case(self):
|
|
return '::'.join(self.chunks)
|
|
|
|
def Dirs(self):
|
|
return '/'.join(self.chunks)
|
|
|
|
def js_enum_case(self):
|
|
result = self.chunks[0].lower()
|
|
for chunk in self.chunks[1:]:
|
|
if not result[-1].isdigit():
|
|
result += '-'
|
|
result += chunk.lower()
|
|
return result
|
|
|
|
|
|
def concat_names(*names):
|
|
return ' '.join([name.canonical_case() for name in names])
|
|
|
|
|
|
class Type:
|
|
def __init__(self, name, json_data, native=False):
|
|
self.json_data = json_data
|
|
self.dict_name = name
|
|
self.name = Name(name, native=native)
|
|
self.category = json_data['category']
|
|
self.is_wire_transparent = False
|
|
|
|
|
|
EnumValue = namedtuple('EnumValue', ['name', 'value', 'valid', 'json_data'])
|
|
|
|
|
|
class EnumType(Type):
|
|
def __init__(self, is_enabled, name, json_data):
|
|
Type.__init__(self, name, json_data)
|
|
|
|
self.values = []
|
|
self.contiguousFromZero = True
|
|
lastValue = -1
|
|
for m in self.json_data['values']:
|
|
if not is_enabled(m):
|
|
continue
|
|
value = m['value']
|
|
if value != lastValue + 1:
|
|
self.contiguousFromZero = False
|
|
lastValue = value
|
|
self.values.append(
|
|
EnumValue(Name(m['name']), value, m.get('valid', True), m))
|
|
|
|
# Assert that all values are unique in enums
|
|
all_values = set()
|
|
for value in self.values:
|
|
if value.value in all_values:
|
|
raise Exception("Duplicate value {} in enum {}".format(
|
|
value.value, name))
|
|
all_values.add(value.value)
|
|
self.is_wire_transparent = True
|
|
|
|
|
|
BitmaskValue = namedtuple('BitmaskValue', ['name', 'value', 'json_data'])
|
|
|
|
|
|
class BitmaskType(Type):
|
|
def __init__(self, is_enabled, name, json_data):
|
|
Type.__init__(self, name, json_data)
|
|
self.values = [
|
|
BitmaskValue(Name(m['name']), m['value'], m)
|
|
for m in self.json_data['values'] if is_enabled(m)
|
|
]
|
|
self.full_mask = 0
|
|
for value in self.values:
|
|
self.full_mask = self.full_mask | value.value
|
|
self.is_wire_transparent = True
|
|
|
|
|
|
class FunctionPointerType(Type):
|
|
def __init__(self, is_enabled, name, json_data):
|
|
Type.__init__(self, name, json_data)
|
|
self.return_type = None
|
|
self.arguments = []
|
|
|
|
|
|
class TypedefType(Type):
|
|
def __init__(self, is_enabled, name, json_data):
|
|
Type.__init__(self, name, json_data)
|
|
self.type = None
|
|
|
|
|
|
class NativeType(Type):
|
|
def __init__(self, is_enabled, name, json_data):
|
|
Type.__init__(self, name, json_data, native=True)
|
|
self.is_wire_transparent = True
|
|
|
|
|
|
# Methods and structures are both "records", so record members correspond to
|
|
# method arguments or structure members.
|
|
class RecordMember:
|
|
def __init__(self,
|
|
name,
|
|
typ,
|
|
annotation,
|
|
json_data,
|
|
optional=False,
|
|
is_return_value=False,
|
|
default_value=None,
|
|
skip_serialize=False):
|
|
self.name = name
|
|
self.type = typ
|
|
self.annotation = annotation
|
|
self.json_data = json_data
|
|
self.length = None
|
|
self.optional = optional
|
|
self.is_return_value = is_return_value
|
|
self.handle_type = None
|
|
self.id_type = None
|
|
self.default_value = default_value
|
|
self.skip_serialize = skip_serialize
|
|
|
|
def set_handle_type(self, handle_type):
|
|
assert self.type.dict_name == "ObjectHandle"
|
|
self.handle_type = handle_type
|
|
|
|
def set_id_type(self, id_type):
|
|
assert self.type.dict_name == "ObjectId"
|
|
self.id_type = id_type
|
|
|
|
|
|
Method = namedtuple(
|
|
'Method', ['name', 'return_type', 'arguments', 'autolock', 'json_data'])
|
|
|
|
|
|
class ObjectType(Type):
|
|
def __init__(self, is_enabled, name, json_data):
|
|
json_data_override = {'methods': []}
|
|
if 'methods' in json_data:
|
|
json_data_override['methods'] = [
|
|
m for m in json_data['methods'] if is_enabled(m)
|
|
]
|
|
Type.__init__(self, name, dict(json_data, **json_data_override))
|
|
|
|
|
|
class Record:
|
|
def __init__(self, name):
|
|
self.name = Name(name)
|
|
self.members = []
|
|
self.may_have_dawn_object = False
|
|
|
|
def update_metadata(self):
|
|
def may_have_dawn_object(member):
|
|
if isinstance(member.type, ObjectType):
|
|
return True
|
|
elif isinstance(member.type, StructureType):
|
|
return member.type.may_have_dawn_object
|
|
else:
|
|
return False
|
|
|
|
self.may_have_dawn_object = any(
|
|
may_have_dawn_object(member) for member in self.members)
|
|
|
|
# Set may_have_dawn_object to true if the type is chained or
|
|
# extensible. Chained structs may contain a Dawn object.
|
|
if isinstance(self, StructureType):
|
|
self.may_have_dawn_object = (self.may_have_dawn_object
|
|
or self.chained or self.extensible)
|
|
|
|
|
|
class StructureType(Record, Type):
|
|
def __init__(self, is_enabled, name, json_data):
|
|
Record.__init__(self, name)
|
|
json_data_override = {}
|
|
if 'members' in json_data:
|
|
json_data_override['members'] = [
|
|
m for m in json_data['members'] if is_enabled(m)
|
|
]
|
|
Type.__init__(self, name, dict(json_data, **json_data_override))
|
|
self.chained = json_data.get('chained', None)
|
|
self.extensible = json_data.get('extensible', None)
|
|
if self.chained:
|
|
assert self.chained == 'in' or self.chained == 'out'
|
|
assert 'chain roots' in json_data
|
|
if self.extensible:
|
|
assert self.extensible == 'in' or self.extensible == 'out'
|
|
# Chained structs inherit from wgpu::ChainedStruct, which has
|
|
# nextInChain, so setting both extensible and chained would result in
|
|
# two nextInChain members.
|
|
assert not (self.extensible and self.chained)
|
|
|
|
def update_metadata(self):
|
|
Record.update_metadata(self)
|
|
|
|
if self.may_have_dawn_object:
|
|
self.is_wire_transparent = False
|
|
return
|
|
|
|
assert not (self.chained or self.extensible)
|
|
|
|
def get_is_wire_transparent(member):
|
|
return member.type.is_wire_transparent and member.annotation == 'value'
|
|
|
|
self.is_wire_transparent = all(
|
|
get_is_wire_transparent(m) for m in self.members)
|
|
|
|
@property
|
|
def output(self):
|
|
return self.chained == "out" or self.extensible == "out"
|
|
|
|
|
|
class ConstantDefinition():
|
|
def __init__(self, is_enabled, name, json_data):
|
|
self.type = None
|
|
self.value = json_data['value']
|
|
self.json_data = json_data
|
|
self.name = Name(name)
|
|
|
|
|
|
class FunctionDeclaration():
|
|
def __init__(self, is_enabled, name, json_data):
|
|
self.return_type = None
|
|
self.arguments = []
|
|
self.json_data = json_data
|
|
self.name = Name(name)
|
|
|
|
|
|
class Command(Record):
|
|
def __init__(self, name, members=None):
|
|
Record.__init__(self, name)
|
|
self.members = members or []
|
|
self.derived_object = None
|
|
self.derived_method = None
|
|
|
|
|
|
def linked_record_members(json_data, types):
|
|
members = []
|
|
members_by_name = {}
|
|
for m in json_data:
|
|
member = RecordMember(Name(m['name']),
|
|
types[m['type']],
|
|
m.get('annotation', 'value'),
|
|
m,
|
|
optional=m.get('optional', False),
|
|
is_return_value=m.get('is_return_value', False),
|
|
default_value=m.get('default', None),
|
|
skip_serialize=m.get('skip_serialize', False))
|
|
handle_type = m.get('handle_type')
|
|
if handle_type:
|
|
member.set_handle_type(types[handle_type])
|
|
id_type = m.get('id_type')
|
|
if id_type:
|
|
member.set_id_type(types[id_type])
|
|
members.append(member)
|
|
members_by_name[member.name.canonical_case()] = member
|
|
|
|
for (member, m) in zip(members, json_data):
|
|
if member.annotation != 'value':
|
|
if not 'length' in m:
|
|
if member.type.category != 'object':
|
|
member.length = "constant"
|
|
member.constant_length = 1
|
|
else:
|
|
assert False
|
|
elif m['length'] == 'strlen':
|
|
member.length = 'strlen'
|
|
elif isinstance(m['length'], int):
|
|
assert m['length'] > 0
|
|
member.length = "constant"
|
|
member.constant_length = m['length']
|
|
else:
|
|
member.length = members_by_name[m['length']]
|
|
|
|
return members
|
|
|
|
|
|
def mark_lengths_non_serializable_lpm(record_members):
|
|
# Remove member length values from command metadata,
|
|
# these are set to the length of the protobuf array.
|
|
for record_member in record_members:
|
|
lengths = set()
|
|
for member in record_member.members:
|
|
lengths.add(member.length)
|
|
|
|
for member in record_member.members:
|
|
if member in lengths:
|
|
member.skip_serialize = True
|
|
|
|
############################################################
|
|
# PARSE
|
|
############################################################
|
|
|
|
|
|
def link_object(obj, types):
|
|
# Disable method's autolock if obj's "no autolock" = True
|
|
obj_scoped_autolock_enabled = not obj.json_data.get('no autolock', False)
|
|
|
|
def make_method(json_data):
|
|
arguments = linked_record_members(json_data.get('args', []), types)
|
|
autolock_enabled = obj_scoped_autolock_enabled and not json_data.get(
|
|
'no autolock', False)
|
|
return Method(Name(json_data['name']),
|
|
types[json_data.get('returns', 'void')], arguments,
|
|
autolock_enabled, json_data)
|
|
|
|
obj.methods = [make_method(m) for m in obj.json_data.get('methods', [])]
|
|
obj.methods.sort(key=lambda method: method.name.canonical_case())
|
|
|
|
|
|
def link_structure(struct, types):
|
|
struct.members = linked_record_members(struct.json_data['members'], types)
|
|
struct.chain_roots = [types[root] for root in struct.json_data.get('chain roots', [])]
|
|
assert all((root.category == 'structure' for root in struct.chain_roots))
|
|
|
|
|
|
def link_function_pointer(function_pointer, types):
|
|
link_function(function_pointer, types)
|
|
|
|
|
|
def link_typedef(typedef, types):
|
|
typedef.type = types[typedef.json_data['type']]
|
|
|
|
|
|
def link_constant(constant, types):
|
|
constant.type = types[constant.json_data['type']]
|
|
assert constant.type.name.native
|
|
|
|
|
|
def link_function(function, types):
|
|
function.return_type = types[function.json_data.get('returns', 'void')]
|
|
function.arguments = linked_record_members(function.json_data['args'],
|
|
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 the sort isn't technically stable).
|
|
#
|
|
# It works by computing for each struct type what is the depth of its DAG of
|
|
# dependents, then re-sorting 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, enabled_tags, disabled_tags=None):
|
|
is_enabled = lambda json_data: item_is_enabled(
|
|
enabled_tags, json_data) and not item_is_disabled(
|
|
disabled_tags, json_data)
|
|
category_to_parser = {
|
|
'bitmask': BitmaskType,
|
|
'enum': EnumType,
|
|
'native': NativeType,
|
|
'function pointer': FunctionPointerType,
|
|
'object': ObjectType,
|
|
'structure': StructureType,
|
|
'typedef': TypedefType,
|
|
'constant': ConstantDefinition,
|
|
'function': FunctionDeclaration
|
|
}
|
|
|
|
types = {}
|
|
|
|
by_category = {}
|
|
for name in category_to_parser.keys():
|
|
by_category[name] = []
|
|
|
|
for (name, json_data) in json.items():
|
|
if name[0] == '_' or not is_enabled(json_data):
|
|
continue
|
|
category = json_data['category']
|
|
parsed = category_to_parser[category](is_enabled, 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 function_pointer in by_category['function pointer']:
|
|
link_function_pointer(function_pointer, types)
|
|
|
|
for typedef in by_category['typedef']:
|
|
link_typedef(typedef, types)
|
|
|
|
for constant in by_category['constant']:
|
|
link_constant(constant, types)
|
|
|
|
for function in by_category['function']:
|
|
link_function(function, 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()
|
|
|
|
api_params = {
|
|
'types': types,
|
|
'by_category': by_category,
|
|
'enabled_tags': enabled_tags,
|
|
'disabled_tags': disabled_tags,
|
|
}
|
|
return {
|
|
'metadata': Metadata(json['_metadata']),
|
|
'types': types,
|
|
'by_category': by_category,
|
|
'enabled_tags': enabled_tags,
|
|
'disabled_tags': disabled_tags,
|
|
'c_methods': lambda typ: c_methods(api_params, typ),
|
|
'c_methods_sorted_by_name': get_c_methods_sorted_by_name(api_params),
|
|
}
|
|
|
|
|
|
############################################################
|
|
# WIRE STUFF
|
|
############################################################
|
|
|
|
|
|
# Create wire commands from api methods
|
|
def compute_wire_params(api_params, wire_json):
|
|
wire_params = api_params.copy()
|
|
types = wire_params['types']
|
|
|
|
commands = []
|
|
return_commands = []
|
|
|
|
wire_json['special items']['client_handwritten_commands'] += wire_json[
|
|
'special items']['client_side_commands']
|
|
|
|
# Generate commands from object methods
|
|
for api_object in wire_params['by_category']['object']:
|
|
for method in api_object.methods:
|
|
command_name = concat_names(api_object.name, method.name)
|
|
command_suffix = Name(command_name).CamelCase()
|
|
|
|
# Only object return values or void are supported.
|
|
# Other methods must be handwritten.
|
|
is_object = method.return_type.category == 'object'
|
|
is_void = method.return_type.name.canonical_case() == 'void'
|
|
if not (is_object or is_void):
|
|
assert command_suffix in (
|
|
wire_json['special items']['client_handwritten_commands'])
|
|
continue
|
|
|
|
if command_suffix in (
|
|
wire_json['special items']['client_side_commands']):
|
|
continue
|
|
|
|
# Create object method commands by prepending "self"
|
|
members = [
|
|
RecordMember(Name('self'), types[api_object.dict_name],
|
|
'value', {})
|
|
]
|
|
members += method.arguments
|
|
|
|
# Client->Server commands that return an object return the
|
|
# result object handle
|
|
if method.return_type.category == 'object':
|
|
result = RecordMember(Name('result'),
|
|
types['ObjectHandle'],
|
|
'value', {},
|
|
is_return_value=True)
|
|
result.set_handle_type(method.return_type)
|
|
members.append(result)
|
|
|
|
command = Command(command_name, members)
|
|
command.derived_object = api_object
|
|
command.derived_method = method
|
|
commands.append(command)
|
|
|
|
for (name, json_data) in wire_json['commands'].items():
|
|
commands.append(Command(name, linked_record_members(json_data, types)))
|
|
|
|
for (name, json_data) in wire_json['return commands'].items():
|
|
return_commands.append(
|
|
Command(name, linked_record_members(json_data, types)))
|
|
|
|
wire_params['cmd_records'] = {
|
|
'command': commands,
|
|
'return command': return_commands
|
|
}
|
|
|
|
for commands in wire_params['cmd_records'].values():
|
|
for command in commands:
|
|
command.update_metadata()
|
|
commands.sort(key=lambda c: c.name.canonical_case())
|
|
|
|
wire_params.update(wire_json.get('special items', {}))
|
|
|
|
return wire_params
|
|
|
|
############################################################
|
|
# DAWN LPM FUZZ STUFF
|
|
############################################################
|
|
|
|
|
|
def compute_lpm_params(api_and_wire_params, lpm_json):
|
|
# Start with all commands in dawn.json and dawn_wire.json
|
|
lpm_params = api_and_wire_params.copy()
|
|
|
|
# Commands that are built through codegen
|
|
generated_commands = []
|
|
|
|
# All commands, including hand written commands that we can't generate
|
|
# through codegen
|
|
all_commands = []
|
|
|
|
# Remove blocklisted commands from protobuf generation params
|
|
blocklisted_cmds_proto = lpm_json.get('blocklisted_cmds')
|
|
custom_cmds_proto = lpm_json.get('custom_cmds')
|
|
for command in lpm_params['cmd_records']['command']:
|
|
blocklisted = command.name.get() in blocklisted_cmds_proto
|
|
custom = command.name.get() in custom_cmds_proto
|
|
|
|
if blocklisted:
|
|
continue
|
|
|
|
if not custom:
|
|
generated_commands.append(command)
|
|
all_commands.append(command)
|
|
|
|
# Set all fields that are marked as the "length" of another field to
|
|
# skip_serialize. The values passed by libprotobuf-mutator will cause
|
|
# an instant crash during serialization if these don't match the length
|
|
# of the data they are passing. These values aren't used in
|
|
# deserialization.
|
|
mark_lengths_non_serializable_lpm(
|
|
api_and_wire_params['cmd_records']['command'])
|
|
mark_lengths_non_serializable_lpm(
|
|
api_and_wire_params['by_category']['structure'])
|
|
|
|
lpm_params['cmd_records'] = {
|
|
'proto_generated_commands': generated_commands,
|
|
'proto_all_commands': all_commands,
|
|
'cpp_generated_commands': generated_commands,
|
|
'lpm_info': lpm_json.get("lpm_info")
|
|
}
|
|
|
|
return lpm_params
|
|
|
|
|
|
def as_protobufTypeLPM(member):
|
|
assert 'type' in member.json_data
|
|
|
|
if member.type.name.native:
|
|
typ = member.json_data['type']
|
|
cpp_to_protobuf_type = {
|
|
"bool": "bool",
|
|
"float": "float",
|
|
"double": "double",
|
|
"int8_t": "int32",
|
|
"int16_t": "int32",
|
|
"int32_t": "int32",
|
|
"int64_t": "int64",
|
|
"uint8_t": "uint32",
|
|
"uint16_t": "uint32",
|
|
"uint32_t": "uint32",
|
|
"uint64_t": "uint64",
|
|
}
|
|
|
|
assert typ in cpp_to_protobuf_type
|
|
|
|
return cpp_to_protobuf_type[typ]
|
|
|
|
return member.type.name.CamelCase()
|
|
|
|
|
|
# Helper that generates names for protobuf grammars from contents
|
|
# of dawn*.json like files. example: membera
|
|
def as_protobufNameLPM(*names):
|
|
# `descriptor` is a reserved keyword in lib-protobuf-mutator
|
|
if (names[0].concatcase() == "descriptor"):
|
|
return "desc"
|
|
return as_varName(*names)
|
|
|
|
|
|
# Helper to generate member accesses within C++ of protobuf objects
|
|
# example: cmd.membera().memberb()
|
|
def as_protobufMemberNameLPM(*names):
|
|
# `descriptor` is a reserved keyword in lib-protobuf-mutator
|
|
if (names[0].concatcase() == "descriptor"):
|
|
return "desc"
|
|
return ''.join([name.concatcase().lower() for name in names])
|
|
|
|
|
|
def unreachable_code():
|
|
assert False
|
|
|
|
|
|
#############################################################
|
|
# Generator
|
|
#############################################################
|
|
|
|
|
|
def as_varName(*names):
|
|
return names[0].camelCase() + ''.join(
|
|
[name.CamelCase() for name in names[1:]])
|
|
|
|
|
|
def as_cType(c_prefix, name):
|
|
if name.native:
|
|
return name.concatcase()
|
|
else:
|
|
return c_prefix + name.CamelCase()
|
|
|
|
|
|
def as_cppType(name):
|
|
if name.native:
|
|
return name.concatcase()
|
|
else:
|
|
return name.CamelCase()
|
|
|
|
|
|
def as_jsEnumValue(value):
|
|
if 'jsrepr' in value.json_data: return value.json_data['jsrepr']
|
|
return "'" + value.name.js_enum_case() + "'"
|
|
|
|
|
|
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}'
|
|
elif typ.category == 'function pointer':
|
|
return 'reinterpret_cast<{}>({})'.format(as_cppType(typ.name), arg)
|
|
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 item_is_enabled(enabled_tags, json_data):
|
|
tags = json_data.get('tags')
|
|
if tags is None: return True
|
|
return any(tag in enabled_tags for tag in tags)
|
|
|
|
|
|
def item_is_disabled(disabled_tags, json_data):
|
|
if disabled_tags is None: return False
|
|
tags = json_data.get('tags')
|
|
if tags is None: return False
|
|
|
|
return any(tag in disabled_tags for tag in tags)
|
|
|
|
|
|
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_MethodSuffix(type_name, method_name):
|
|
assert not type_name.native and not method_name.native
|
|
return type_name.CamelCase() + method_name.CamelCase()
|
|
|
|
|
|
def as_frontendType(metadata, typ):
|
|
if typ.category == 'object':
|
|
return typ.name.CamelCase() + 'Base*'
|
|
elif typ.category in ['bitmask', 'enum']:
|
|
return metadata.namespace + '::' + typ.name.CamelCase()
|
|
elif typ.category == 'structure':
|
|
return as_cppType(typ.name)
|
|
else:
|
|
return as_cType(metadata.c_prefix, typ.name)
|
|
|
|
|
|
def as_wireType(metadata, typ):
|
|
if typ.category == 'object':
|
|
return typ.name.CamelCase() + '*'
|
|
elif typ.category in ['bitmask', 'enum', 'structure']:
|
|
return metadata.c_prefix + typ.name.CamelCase()
|
|
else:
|
|
return as_cppType(typ.name)
|
|
|
|
|
|
def as_formatType(typ):
|
|
# Unsigned integral types
|
|
if typ.json_data['type'] in ['bool', 'uint32_t', 'uint64_t']:
|
|
return 'u'
|
|
|
|
# Defaults everything else to strings.
|
|
return 's'
|
|
|
|
|
|
def c_methods(params, typ):
|
|
return typ.methods + [
|
|
Method(Name('reference'), params['types']['void'], [], False, {}),
|
|
Method(Name('release'), params['types']['void'], [], False, {}),
|
|
]
|
|
|
|
def get_c_methods_sorted_by_name(api_params):
|
|
unsorted = [(as_MethodSuffix(typ.name, method.name), typ, method) \
|
|
for typ in api_params['by_category']['object'] \
|
|
for method in c_methods(api_params, typ) ]
|
|
return [(typ, method) for (_, typ, method) in sorted(unsorted)]
|
|
|
|
|
|
def has_callback_arguments(method):
|
|
return any(arg.type.category == 'function pointer' for arg in method.arguments)
|
|
|
|
|
|
def make_base_render_params(metadata):
|
|
c_prefix = metadata.c_prefix
|
|
|
|
def as_cTypeEnumSpecialCase(typ):
|
|
if typ.category == 'bitmask':
|
|
return as_cType(c_prefix, typ.name) + 'Flags'
|
|
return as_cType(c_prefix, typ.name)
|
|
|
|
def as_cEnum(type_name, value_name):
|
|
assert not type_name.native and not value_name.native
|
|
return c_prefix + type_name.CamelCase() + '_' + value_name.CamelCase()
|
|
|
|
def as_cMethod(type_name, method_name):
|
|
c_method = c_prefix.lower()
|
|
if type_name != None:
|
|
assert not type_name.native
|
|
c_method += type_name.CamelCase()
|
|
assert not method_name.native
|
|
c_method += method_name.CamelCase()
|
|
return c_method
|
|
|
|
def as_cProc(type_name, method_name):
|
|
c_proc = c_prefix + 'Proc'
|
|
if type_name != None:
|
|
assert not type_name.native
|
|
c_proc += type_name.CamelCase()
|
|
assert not method_name.native
|
|
c_proc += method_name.CamelCase()
|
|
return c_proc
|
|
|
|
return {
|
|
'Name': lambda name: Name(name),
|
|
'as_annotated_cType': \
|
|
lambda arg: annotated(as_cTypeEnumSpecialCase(arg.type), 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': lambda name: as_cType(c_prefix, name),
|
|
'as_cppType': as_cppType,
|
|
'as_jsEnumValue': as_jsEnumValue,
|
|
'convert_cType_to_cppType': convert_cType_to_cppType,
|
|
'as_varName': as_varName,
|
|
'decorate': decorate,
|
|
'as_formatType': as_formatType
|
|
}
|
|
|
|
|
|
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', 'cpp_headers', 'cpp', 'proc', 'mock_api', 'wire',
|
|
'native_utils', 'dawn_lpmfuzz_cpp', 'dawn_lpmfuzz_proto'
|
|
]
|
|
|
|
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("--lpm-json",
|
|
default=None,
|
|
type=str,
|
|
help='The DAWN LPM FUZZER definitions to use.')
|
|
parser.add_argument(
|
|
'--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())
|
|
|
|
targets = args.targets.split(',')
|
|
|
|
wire_json = None
|
|
if args.wire_json:
|
|
with open(args.wire_json) as f:
|
|
wire_json = json.loads(f.read())
|
|
|
|
lpm_json = None
|
|
if args.lpm_json:
|
|
with open(args.lpm_json) as f:
|
|
lpm_json = json.loads(f.read())
|
|
|
|
renders = []
|
|
|
|
params_dawn = parse_json(loaded_json,
|
|
enabled_tags=['dawn', 'native', 'deprecated'])
|
|
metadata = params_dawn['metadata']
|
|
RENDER_PARAMS_BASE = make_base_render_params(metadata)
|
|
|
|
api = metadata.api.lower()
|
|
prefix = metadata.proc_table_prefix.lower()
|
|
if 'headers' in targets:
|
|
renders.append(
|
|
FileRender('api.h', 'include/dawn/' + api + '.h',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
renders.append(
|
|
FileRender('dawn_proc_table.h',
|
|
'include/dawn/' + prefix + '_proc_table.h',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
|
|
if 'cpp_headers' in targets:
|
|
renders.append(
|
|
FileRender('api_cpp.h', 'include/dawn/' + api + '_cpp.h',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
|
|
renders.append(
|
|
FileRender('api_cpp_print.h',
|
|
'include/dawn/' + api + '_cpp_print.h',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
|
|
renders.append(
|
|
FileRender('api_cpp_chained_struct.h',
|
|
'include/dawn/' + api + '_cpp_chained_struct.h',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
|
|
if 'proc' in targets:
|
|
renders.append(
|
|
FileRender('dawn_proc.c', 'src/dawn/' + prefix + '_proc.c',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
renders.append(
|
|
FileRender('dawn_thread_dispatch_proc.cpp',
|
|
'src/dawn/' + prefix + '_thread_dispatch_proc.cpp',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
|
|
if 'webgpu_dawn_native_proc' in targets:
|
|
renders.append(
|
|
FileRender('dawn/native/api_dawn_native_proc.cpp',
|
|
'src/dawn/native/webgpu_dawn_native_proc.cpp',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
|
|
if 'cpp' in targets:
|
|
renders.append(
|
|
FileRender('api_cpp.cpp', 'src/dawn/' + api + '_cpp.cpp',
|
|
[RENDER_PARAMS_BASE, params_dawn]))
|
|
|
|
if 'webgpu_headers' in targets:
|
|
params_upstream = parse_json(loaded_json,
|
|
enabled_tags=['upstream', 'native'],
|
|
disabled_tags=['dawn'])
|
|
renders.append(
|
|
FileRender('api.h', 'webgpu-headers/' + api + '.h',
|
|
[RENDER_PARAMS_BASE, params_upstream]))
|
|
|
|
if 'emscripten_bits' in targets:
|
|
params_emscripten = parse_json(loaded_json,
|
|
enabled_tags=['emscripten'])
|
|
renders.append(
|
|
FileRender('api.h', 'emscripten-bits/' + api + '.h',
|
|
[RENDER_PARAMS_BASE, params_emscripten]))
|
|
renders.append(
|
|
FileRender('api_cpp.h', 'emscripten-bits/' + api + '_cpp.h',
|
|
[RENDER_PARAMS_BASE, params_emscripten]))
|
|
renders.append(
|
|
FileRender('api_cpp.cpp', 'emscripten-bits/' + api + '_cpp.cpp',
|
|
[RENDER_PARAMS_BASE, params_emscripten]))
|
|
renders.append(
|
|
FileRender('api_struct_info.json',
|
|
'emscripten-bits/' + api + '_struct_info.json',
|
|
[RENDER_PARAMS_BASE, params_emscripten]))
|
|
renders.append(
|
|
FileRender('library_api_enum_tables.js',
|
|
'emscripten-bits/library_' + api + '_enum_tables.js',
|
|
[RENDER_PARAMS_BASE, params_emscripten]))
|
|
|
|
if 'mock_api' in targets:
|
|
mock_params = [
|
|
RENDER_PARAMS_BASE, params_dawn, {
|
|
'has_callback_arguments': has_callback_arguments
|
|
}
|
|
]
|
|
renders.append(
|
|
FileRender('mock_api.h', 'src/dawn/mock_' + api + '.h',
|
|
mock_params))
|
|
renders.append(
|
|
FileRender('mock_api.cpp', 'src/dawn/mock_' + api + '.cpp',
|
|
mock_params))
|
|
|
|
if 'native_utils' in targets:
|
|
frontend_params = [
|
|
RENDER_PARAMS_BASE,
|
|
params_dawn,
|
|
{
|
|
# TODO: as_frontendType and co. take a Type, not a Name :(
|
|
'as_frontendType': lambda typ: as_frontendType(metadata, typ),
|
|
'as_annotated_frontendType': \
|
|
lambda arg: annotated(as_frontendType(metadata, arg.type), arg),
|
|
}
|
|
]
|
|
|
|
impl_dir = metadata.impl_dir + '/' if metadata.impl_dir else ''
|
|
native_dir = impl_dir + Name(metadata.native_namespace).Dirs()
|
|
namespace = metadata.namespace
|
|
renders.append(
|
|
FileRender('dawn/native/ValidationUtils.h',
|
|
'src/' + native_dir + '/ValidationUtils_autogen.h',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/ValidationUtils.cpp',
|
|
'src/' + native_dir + '/ValidationUtils_autogen.cpp',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/dawn_platform.h',
|
|
'src/' + native_dir + '/' + prefix + '_platform_autogen.h',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/api_structs.h',
|
|
'src/' + native_dir + '/' + namespace + '_structs_autogen.h',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/api_structs.cpp',
|
|
'src/' + native_dir + '/' + namespace + '_structs_autogen.cpp',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/ProcTable.cpp',
|
|
'src/' + native_dir + '/ProcTable.cpp', frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/ChainUtils.h',
|
|
'src/' + native_dir + '/ChainUtils_autogen.h',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/ChainUtils.cpp',
|
|
'src/' + native_dir + '/ChainUtils_autogen.cpp',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/api_absl_format.h',
|
|
'src/' + native_dir + '/' + api + '_absl_format_autogen.h',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/api_absl_format.cpp',
|
|
'src/' + native_dir + '/' + api + '_absl_format_autogen.cpp',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender(
|
|
'dawn/native/api_StreamImpl.cpp', 'src/' + native_dir +
|
|
'/' + api + '_StreamImpl_autogen.cpp', frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/ObjectType.h',
|
|
'src/' + native_dir + '/ObjectType_autogen.h',
|
|
frontend_params))
|
|
renders.append(
|
|
FileRender('dawn/native/ObjectType.cpp',
|
|
'src/' + native_dir + '/ObjectType_autogen.cpp',
|
|
frontend_params))
|
|
|
|
if 'wire' in targets:
|
|
params_dawn_wire = parse_json(loaded_json,
|
|
enabled_tags=['dawn', 'deprecated'],
|
|
disabled_tags=['native'])
|
|
additional_params = compute_wire_params(params_dawn_wire,
|
|
wire_json)
|
|
|
|
wire_params = [
|
|
RENDER_PARAMS_BASE, params_dawn_wire, {
|
|
'as_wireType': lambda type : as_wireType(metadata, type),
|
|
'as_annotated_wireType': \
|
|
lambda arg: annotated(as_wireType(metadata, arg.type), arg),
|
|
}, additional_params
|
|
]
|
|
renders.append(
|
|
FileRender('dawn/wire/ObjectType.h',
|
|
'src/dawn/wire/ObjectType_autogen.h', wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/WireCmd.h',
|
|
'src/dawn/wire/WireCmd_autogen.h', wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/WireCmd.cpp',
|
|
'src/dawn/wire/WireCmd_autogen.cpp', wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/client/ApiObjects.h',
|
|
'src/dawn/wire/client/ApiObjects_autogen.h',
|
|
wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/client/ApiProcs.cpp',
|
|
'src/dawn/wire/client/ApiProcs_autogen.cpp',
|
|
wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/client/ClientBase.h',
|
|
'src/dawn/wire/client/ClientBase_autogen.h',
|
|
wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/client/ClientHandlers.cpp',
|
|
'src/dawn/wire/client/ClientHandlers_autogen.cpp',
|
|
wire_params))
|
|
renders.append(
|
|
FileRender(
|
|
'dawn/wire/client/ClientPrototypes.inc',
|
|
'src/dawn/wire/client/ClientPrototypes_autogen.inc',
|
|
wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/server/ServerBase.h',
|
|
'src/dawn/wire/server/ServerBase_autogen.h',
|
|
wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/server/ServerDoers.cpp',
|
|
'src/dawn/wire/server/ServerDoers_autogen.cpp',
|
|
wire_params))
|
|
renders.append(
|
|
FileRender('dawn/wire/server/ServerHandlers.cpp',
|
|
'src/dawn/wire/server/ServerHandlers_autogen.cpp',
|
|
wire_params))
|
|
renders.append(
|
|
FileRender(
|
|
'dawn/wire/server/ServerPrototypes.inc',
|
|
'src/dawn/wire/server/ServerPrototypes_autogen.inc',
|
|
wire_params))
|
|
|
|
if 'dawn_lpmfuzz_proto' in targets:
|
|
params_dawn_wire = parse_json(loaded_json,
|
|
enabled_tags=['dawn', 'deprecated'],
|
|
disabled_tags=['native'])
|
|
api_and_wire_params = compute_wire_params(params_dawn_wire,
|
|
wire_json)
|
|
|
|
fuzzer_params = compute_lpm_params(api_and_wire_params, lpm_json)
|
|
|
|
lpm_params = [
|
|
RENDER_PARAMS_BASE, params_dawn_wire, {
|
|
'as_protobufTypeLPM': as_protobufTypeLPM,
|
|
'as_protobufNameLPM': as_protobufNameLPM,
|
|
'unreachable': unreachable_code
|
|
}, api_and_wire_params, fuzzer_params
|
|
]
|
|
|
|
renders.append(
|
|
FileRender('dawn/fuzzers/lpmfuzz/dawn_lpm.proto',
|
|
'src/dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.proto',
|
|
lpm_params))
|
|
|
|
renders.append(
|
|
FileRender(
|
|
'dawn/fuzzers/lpmfuzz/dawn_object_types_lpm.proto',
|
|
'src/dawn/fuzzers/lpmfuzz/dawn_object_types_lpm_autogen.proto',
|
|
lpm_params))
|
|
|
|
if 'dawn_lpmfuzz_cpp' in targets:
|
|
params_dawn_wire = parse_json(loaded_json,
|
|
enabled_tags=['dawn', 'deprecated'],
|
|
disabled_tags=['native'])
|
|
api_and_wire_params = compute_wire_params(params_dawn_wire,
|
|
wire_json)
|
|
|
|
fuzzer_params = compute_lpm_params(api_and_wire_params, lpm_json)
|
|
|
|
lpm_params = [
|
|
RENDER_PARAMS_BASE, params_dawn_wire, {
|
|
'as_protobufMemberName': as_protobufMemberNameLPM,
|
|
'unreachable_code': unreachable_code
|
|
}, api_and_wire_params, fuzzer_params
|
|
]
|
|
|
|
renders.append(
|
|
FileRender(
|
|
'dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp',
|
|
'src/dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.cpp',
|
|
lpm_params))
|
|
|
|
renders.append(
|
|
FileRender(
|
|
'dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h',
|
|
'src/dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h',
|
|
lpm_params))
|
|
|
|
renders.append(
|
|
FileRender(
|
|
'dawn/fuzzers/lpmfuzz/DawnLPMConstants.h',
|
|
'src/dawn/fuzzers/lpmfuzz/DawnLPMConstants_autogen.h',
|
|
lpm_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)]
|
|
if args.lpm_json != None:
|
|
deps += [os.path.abspath(args.lpm_json)]
|
|
return deps
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(run_generator(MultiGeneratorFromDawnJSON()))
|