mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-14 11:21:40 +00:00
1. Auto-generate mutex locking code in DawnNative's ProcTable's functions. Using a mutex owned by the related Device. - Unless the function/class has "no autolock" attribute (new) in dawn.json. In which cases, no locking code will be auto-generated. - Currently Instance, Adapter, Surface, Encoder classes and Reference/Release method have "no autolock". 2. Added Feature::ImplicitDeviceSynchronization to enable Device's mutex. - If this feature is disabled, lock/unlock Device's mutex is no-op. Auto-generated locking code will have no effect. This is the default. - This approach is used instead of generating two sets of ProcTable because it's cleaner and the internal code doesn't need to care about whether there is a mutex being locked or not. Furthermore, if there were two sets of ProcTable, and user used dawnProcSetProcs() to set global ProcTable, it would affect all other modules using different Devices. Even though those modules don't need thread safety. Bug: dawn:1662 Change-Id: I75f0d28959f333318e4159916b259131581f79f5 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/119940 Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Quyen Le <lehoangquyen@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org>
1177 lines
42 KiB
Python
1177 lines
42 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.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
|
|
|
|
|
|
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])
|
|
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
|
|
|
|
|
|
############################################################
|
|
# 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 generation
|
|
proto_generated_commands = []
|
|
|
|
# All commands, including hand written commands that we can't generate
|
|
# through codegen
|
|
proto_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 not blocklisted and not custom:
|
|
proto_generated_commands.append(command)
|
|
proto_all_commands.append(command)
|
|
|
|
lpm_params['cmd_records'] = {
|
|
'proto_generated_commands': proto_generated_commands,
|
|
'proto_all_commands': proto_all_commands,
|
|
}
|
|
|
|
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()
|
|
|
|
|
|
def as_protobufNameLPM(*names):
|
|
# `descriptor` is a reserved keyword in lpm
|
|
if (names[0].concatcase() == "descriptor"):
|
|
return "desc"
|
|
return as_varName(*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 + [
|
|
x for x in [
|
|
Method(Name('reference'), params['types']['void'], [], False,
|
|
{'tags': ['dawn', 'emscripten']}),
|
|
Method(Name('release'), params['types']['void'], [], False,
|
|
{'tags': ['dawn', 'emscripten']}),
|
|
] if item_is_enabled(params['enabled_tags'], x.json_data)
|
|
and not item_is_disabled(params['disabled_tags'], x.json_data)
|
|
]
|
|
|
|
|
|
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]))
|
|
|
|
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))
|
|
|
|
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)
|
|
|
|
lpm_params = [
|
|
RENDER_PARAMS_BASE, params_dawn_wire, {}, api_and_wire_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()))
|