Add wire_cmd.py and dawn_wire.json to autogenerate all wire commands.

Unify code generation for Client->Server and Server->Client commands.
Methods in dawn.json are converted into command records and additional
commands are specified in dawn_wire.json. This can then be used to
completely generate the command handlers and command struct definitions.

Bug: dawn:88
Change-Id: Ic796796ede0aafe02e14f1f96790324dad92f4c0
Reviewed-on: https://dawn-review.googlesource.com/c/3800
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Austin Eng 2019-01-15 20:49:53 +00:00 committed by Commit Bot service account
parent a483fac90d
commit c7f416c0c9
14 changed files with 822 additions and 661 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
*.pyc
# Directories added by gclient sync and the GN build # Directories added by gclient sync and the GN build
.gclient .gclient
.gclient_entries .gclient_entries

View File

@ -54,6 +54,8 @@ template("dawn_generator") {
# target using templates in this directory. # target using templates in this directory.
generator_args = [ generator_args = [
rebase_path("dawn.json", root_build_dir), rebase_path("dawn.json", root_build_dir),
"--wire-json",
rebase_path("dawn_wire.json", root_build_dir),
"--template-dir", "--template-dir",
rebase_path("generator/templates", root_build_dir), rebase_path("generator/templates", root_build_dir),
"--targets", "--targets",
@ -695,8 +697,8 @@ dawn_static_and_shared_library("libdawn_native") {
] ]
deps = [ deps = [
":libdawn_native_sources",
":dawn_common", ":dawn_common",
":libdawn_native_sources",
] ]
sources = [ sources = [
"src/dawn_native/DawnNative.cpp", "src/dawn_native/DawnNative.cpp",
@ -761,7 +763,10 @@ dawn_static_and_shared_library("libdawn_wire") {
configs = [ ":dawn_internal" ] configs = [ ":dawn_internal" ]
sources = get_target_outputs(":libdawn_wire_gen") sources = get_target_outputs(":libdawn_wire_gen")
sources += [ "src/dawn_wire/WireCmd.h" ] sources += [
"src/dawn_wire/WireDeserializeAllocator.cpp",
"src/dawn_wire/WireDeserializeAllocator.h",
]
# Make headers publically visible # Make headers publically visible
public_deps = [ public_deps = [
@ -849,8 +854,8 @@ test("dawn_unittests") {
":dawn_headers", ":dawn_headers",
":dawn_utils", ":dawn_utils",
":libdawn", ":libdawn",
":libdawn_native_sources",
":libdawn_native", ":libdawn_native",
":libdawn_native_sources",
":libdawn_wire", ":libdawn_wire",
":mock_dawn_gen", ":mock_dawn_gen",
"third_party:gmock_and_gtest", "third_party:gmock_and_gtest",

View File

@ -1106,6 +1106,18 @@
{"value": 11, "name": "unorm r8 g8"} {"value": 11, "name": "unorm r8 g8"}
] ]
}, },
"ObjectType": {
"_comment": "Only used for the wire",
"category": "native"
},
"ObjectId": {
"_comment": "Only used for the wire",
"category": "native"
},
"ObjectHandle": {
"_comment": "Only used for the wire",
"category": "native"
},
"void": { "void": {
"category": "native" "category": "native"
}, },

80
dawn_wire.json Normal file
View File

@ -0,0 +1,80 @@
{
"_comment": [
"Copyright 2019 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."
],
"commands": {
"buffer map async": [
{ "name": "buffer id", "type": "ObjectId" },
{ "name": "request serial", "type": "uint32_t" },
{ "name": "start", "type": "uint32_t" },
{ "name": "size", "type": "uint32_t" },
{ "name": "is write", "type": "bool" }
],
"buffer update mapped data": [
{ "name": "buffer id", "type": "ObjectId" },
{ "name": "data length", "type": "uint32_t" },
{ "name": "data", "type": "uint8_t", "annotation": "const*", "length": "data length" }
],
"destroy object": [
{ "name": "object type", "type": "ObjectType" },
{ "name": "object id", "type": "ObjectId" }
]
},
"return commands": {
"buffer map read async callback": [
{ "name": "buffer", "type": "ObjectHandle" },
{ "name": "request serial", "type": "uint32_t" },
{ "name": "status", "type": "uint32_t" },
{ "name": "data length", "type": "uint32_t" },
{ "name": "data", "type": "uint8_t", "annotation": "const*", "length": "data length" }
],
"buffer map write async callback": [
{ "name": "buffer", "type": "ObjectHandle" },
{ "name": "request serial", "type": "uint32_t" },
{ "name": "status", "type": "uint32_t" }
],
"device error callback": [
{ "name": "message", "type": "char", "annotation": "const*", "length": "strlen" }
],
"fence update completed value": [
{ "name": "fence", "type": "ObjectHandle" },
{ "name": "value", "type": "uint64_t" }
]
},
"special items": {
"client_side_commands": [
"FenceGetCompletedValue"
],
"client_proxied_commands": [
"BufferUnmap",
"DeviceCreateFence",
"QueueSignal"
],
"client_special_objects": [
"Buffer",
"Device",
"Fence"
],
"server_custom_pre_handler_commands": [
"BufferUnmap"
],
"server_custom_post_handler_commands": [
"QueueSignal"
],
"server_reverse_lookup_objects": [
"Fence"
]
}
}

149
generator/common.py Normal file
View File

@ -0,0 +1,149 @@
# 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.
from collections import namedtuple
class Name:
def __init__(self, name, native=False):
self.native = native
if native:
self.chunks = [name]
else:
self.chunks = name.split(' ')
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)
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_builder = self.name.canonical_case().endswith(" builder")
EnumValue = namedtuple('EnumValue', ['name', 'value'])
class EnumType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.values = [EnumValue(Name(m['name']), m['value']) for m in self.json_data['values']]
BitmaskValue = namedtuple('BitmaskValue', ['name', 'value'])
class BitmaskType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.values = [BitmaskValue(Name(m['name']), m['value']) for m in self.json_data['values']]
self.full_mask = 0
for value in self.values:
self.full_mask = self.full_mask | value.value
class NativeType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data, native=True)
class NativelyDefined(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
# Methods and structures are both "records", so record members correspond to
# method arguments or structure members.
class RecordMember:
def __init__(self, name, typ, annotation, optional, is_return_value):
self.name = name
self.type = typ
self.annotation = annotation
self.length = None
self.optional = optional
self.is_return_value = is_return_value
Method = namedtuple('Method', ['name', 'return_type', 'arguments'])
class ObjectType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.methods = []
self.native_methods = []
self.built_type = None
class Record:
def __init__(self, name):
self.name = Name(name)
self.members = []
self.has_dawn_object = False
def update_metadata(self):
def has_dawn_object(member):
if isinstance(member.type, ObjectType):
return True
elif isinstance(member.type, StructureType):
return member.type.has_dawn_object
else:
return False
self.has_dawn_object = any(has_dawn_object(member) for member in self.members)
class StructureType(Record, Type):
def __init__(self, name, json_data):
Record.__init__(self, name)
Type.__init__(self, name, json_data)
self.extensible = json_data.get("extensible", False)
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.get('optional', False),
m.get('is_return_value', False))
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 == 'structure':
member.length = "constant"
member.constant_length = 1
else:
assert(False)
elif m['length'] == 'strlen':
member.length = 'strlen'
else:
member.length = members_by_name[m['length']]
return members

View File

@ -17,90 +17,9 @@
# COMMON # COMMON
############################################################ ############################################################
from collections import namedtuple from collections import namedtuple
from common import Name
class Name: import common
def __init__(self, name, native=False): import wire_cmd
self.native = native
if native:
self.chunks = [name]
else:
self.chunks = name.split(' ')
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)
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_builder = self.name.canonical_case().endswith(" builder")
EnumValue = namedtuple('EnumValue', ['name', 'value'])
class EnumType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.values = [EnumValue(Name(m['name']), m['value']) for m in self.json_data['values']]
BitmaskValue = namedtuple('BitmaskValue', ['name', 'value'])
class BitmaskType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.values = [BitmaskValue(Name(m['name']), m['value']) for m in self.json_data['values']]
self.full_mask = 0
for value in self.values:
self.full_mask = self.full_mask | value.value
class NativeType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data, native=True)
class NativelyDefined(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
# Methods and structures are both "records", so record members correspond to
# method arguments or structure members.
class RecordMember:
def __init__(self, name, typ, annotation, optional):
self.name = name
self.type = typ
self.annotation = annotation
self.length = None
self.optional = optional
Method = namedtuple('Method', ['name', 'return_type', 'arguments'])
class ObjectType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.methods = []
self.native_methods = []
self.built_type = None
class StructureType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.extensible = json_data.get("extensible", False)
self.members = []
############################################################ ############################################################
# PARSE # PARSE
@ -111,35 +30,10 @@ def is_native_method(method):
return method.return_type.category == "natively defined" or \ return method.return_type.category == "natively defined" or \
any([arg.type.category == "natively defined" for arg in method.arguments]) any([arg.type.category == "natively defined" for arg in method.arguments])
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.get('optional', False))
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 == 'structure':
member.length = "constant"
member.constant_length = 1
else:
assert(False)
elif m['length'] == 'strlen':
member.length = 'strlen'
else:
member.length = members_by_name[m['length']]
return members
def link_object(obj, types): def link_object(obj, types):
def make_method(json_data): def make_method(json_data):
arguments = linked_record_members(json_data.get('args', []), types) arguments = common.linked_record_members(json_data.get('args', []), types)
return Method(Name(json_data['name']), types[json_data.get('returns', 'void')], arguments) 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', [])] 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.methods = [method for method in methods if not is_native_method(method)]
@ -154,7 +48,7 @@ def link_object(obj, types):
assert(obj.built_type != None) assert(obj.built_type != None)
def link_structure(struct, types): def link_structure(struct, types):
struct.members = linked_record_members(struct.json_data['members'], 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 # 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 # This is a form of topological sort where we try to keep the order reasonably similar to the
@ -194,12 +88,12 @@ def topo_sort_structure(structs):
def parse_json(json): def parse_json(json):
category_to_parser = { category_to_parser = {
'bitmask': BitmaskType, 'bitmask': common.BitmaskType,
'enum': EnumType, 'enum': common.EnumType,
'native': NativeType, 'native': common.NativeType,
'natively defined': NativelyDefined, 'natively defined': common.NativelyDefined,
'object': ObjectType, 'object': common.ObjectType,
'structure': StructureType, 'structure': common.StructureType,
} }
types = {} types = {}
@ -227,6 +121,9 @@ def parse_json(json):
by_category['structure'] = topo_sort_structure(by_category['structure']) by_category['structure'] = topo_sort_structure(by_category['structure'])
for struct in by_category['structure']:
struct.update_metadata()
return { return {
'types': types, 'types': types,
'by_category': by_category 'by_category': by_category
@ -392,18 +289,18 @@ def cpp_native_methods(types, typ):
methods = typ.methods + typ.native_methods methods = typ.methods + typ.native_methods
if typ.is_builder: if typ.is_builder:
methods.append(Method(Name('set error callback'), types['void'], [ methods.append(common.Method(Name('set error callback'), types['void'], [
RecordMember(Name('callback'), types['builder error callback'], 'value', False), common.RecordMember(Name('callback'), types['builder error callback'], 'value', False, False),
RecordMember(Name('userdata1'), types['callback userdata'], 'value', False), common.RecordMember(Name('userdata1'), types['callback userdata'], 'value', False, False),
RecordMember(Name('userdata2'), types['callback userdata'], 'value', False), common.RecordMember(Name('userdata2'), types['callback userdata'], 'value', False, False),
])) ]))
return methods return methods
def c_native_methods(types, typ): def c_native_methods(types, typ):
return cpp_native_methods(types, typ) + [ return cpp_native_methods(types, typ) + [
Method(Name('reference'), types['void'], []), common.Method(Name('reference'), types['void'], []),
Method(Name('release'), types['void'], []), common.Method(Name('release'), types['void'], []),
] ]
def js_native_methods(types, typ): def js_native_methods(types, typ):
@ -412,7 +309,7 @@ def js_native_methods(types, typ):
def debug(text): def debug(text):
print(text) print(text)
def get_renders_for_targets(api_params, targets): def get_renders_for_targets(api_params, wire_json, targets):
base_params = { base_params = {
'enumerate': enumerate, 'enumerate': enumerate,
'format': format, 'format': format,
@ -471,17 +368,20 @@ def get_renders_for_targets(api_params, targets):
renders.append(FileRender('dawn_native/ProcTable.cpp', 'dawn_native/ProcTable.cpp', frontend_params)) renders.append(FileRender('dawn_native/ProcTable.cpp', 'dawn_native/ProcTable.cpp', frontend_params))
if 'dawn_wire' in targets: if 'dawn_wire' in targets:
additional_params = wire_cmd.compute_wire_params(api_params, wire_json)
wire_params = [ wire_params = [
base_params, base_params,
api_params, api_params,
c_params, c_params,
{ {
'as_wireType': lambda typ: typ.name.CamelCase() + '*' if typ.category == 'object' else as_cppType(typ.name) 'as_wireType': lambda typ: typ.name.CamelCase() + '*' if typ.category == 'object' else as_cppType(typ.name)
} },
additional_params
] ]
renders.append(FileRender('dawn_wire/TypeTraits.h', 'dawn_wire/TypeTraits_autogen.h', wire_params))
renders.append(FileRender('dawn_wire/WireCmd.h', 'dawn_wire/WireCmd_autogen.h', wire_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/WireCmd.cpp', 'dawn_wire/WireCmd_autogen.cpp', wire_params))
renders.append(FileRender('dawn_wire/TypeTraits.h', 'dawn_wire/TypeTraits_autogen.h', wire_params))
renders.append(FileRender('dawn_wire/WireClient.cpp', 'dawn_wire/WireClient.cpp', wire_params)) renders.append(FileRender('dawn_wire/WireClient.cpp', 'dawn_wire/WireClient.cpp', wire_params))
renders.append(FileRender('dawn_wire/WireServer.cpp', 'dawn_wire/WireServer.cpp', wire_params)) renders.append(FileRender('dawn_wire/WireServer.cpp', 'dawn_wire/WireServer.cpp', wire_params))
@ -507,6 +407,7 @@ def main():
formatter_class = argparse.ArgumentDefaultsHelpFormatter formatter_class = argparse.ArgumentDefaultsHelpFormatter
) )
parser.add_argument('json', metavar='DAWN_JSON', nargs=1, type=str, help ='The DAWN JSON definition to use.') parser.add_argument('json', metavar='DAWN_JSON', nargs=1, 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', '--template-dir', default='templates', type=str, help='Directory with template files.') parser.add_argument('-t', '--template-dir', default='templates', type=str, help='Directory with template files.')
parser.add_argument('-T', '--targets', required=True, type=str, help='Comma-separated subset of targets to output. Available targets: ' + ', '.join(allowed_targets)) parser.add_argument('-T', '--targets', required=True, type=str, help='Comma-separated subset of targets to output. Available targets: ' + ', '.join(allowed_targets))
parser.add_argument(kExtraPythonPath, default=None, type=str, help='Additional python path to set before loading Jinja2') parser.add_argument(kExtraPythonPath, default=None, type=str, help='Additional python path to set before loading Jinja2')
@ -522,7 +423,17 @@ def main():
api_params = parse_json(loaded_json) api_params = parse_json(loaded_json)
targets = args.targets.split(',') targets = args.targets.split(',')
renders = get_renders_for_targets(api_params, targets) dependencies = [
os.path.join(os.path.abspath(os.path.dirname(__file__)), "common.py")
]
loaded_wire_json = None
if args.wire_json:
with open(args.wire_json) as f:
loaded_wire_json = json.loads(f.read())
dependencies.append(args.wire_json)
renders = get_renders_for_targets(api_params, loaded_wire_json, targets)
# The caller wants to assert that the outputs are what it expects. # The caller wants to assert that the outputs are what it expects.
# Load the file and compare with our renders. # Load the file and compare with our renders.
@ -544,8 +455,9 @@ def main():
if args.output_json_tarball != None: if args.output_json_tarball != None:
output_to_json(outputs, args.output_json_tarball) output_to_json(outputs, args.output_json_tarball)
dependencies = [args.template_dir + os.path.sep + render.template for render in renders] dependencies += [args.template_dir + os.path.sep + render.template for render in renders]
dependencies.append(args.json[0]) dependencies.append(args.json[0])
dependencies.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), "wire_cmd.py"))
output_depfile(args.depfile, args.output_json_tarball, dependencies) output_depfile(args.depfile, args.output_json_tarball, dependencies)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -13,7 +13,8 @@
//* limitations under the License. //* limitations under the License.
#include "dawn_wire/Wire.h" #include "dawn_wire/Wire.h"
#include "dawn_wire/WireCmd.h" #include "dawn_wire/WireCmd_autogen.h"
#include "dawn_wire/WireDeserializeAllocator.h"
#include "common/Assert.h" #include "common/Assert.h"
#include "common/SerialMap.h" #include "common/SerialMap.h"
@ -66,12 +67,7 @@ namespace dawn_wire {
BuilderCallbackData builderCallback; BuilderCallbackData builderCallback;
}; };
{% set special_objects = [ {% for type in by_category["object"] if not type.name.CamelCase() in client_special_objects %}
"device",
"buffer",
"fence",
] %}
{% for type in by_category["object"] if not type.name.canonical_case() in special_objects %}
struct {{type.name.CamelCase()}} : ObjectBase { struct {{type.name.CamelCase()}} : ObjectBase {
using ObjectBase::ObjectBase; using ObjectBase::ObjectBase;
}; };
@ -269,8 +265,6 @@ namespace dawn_wire {
CommandSerializer* mSerializer = nullptr; CommandSerializer* mSerializer = nullptr;
}; };
{% set client_side_commands = ["FenceGetCompletedValue"] %}
//* Implementation of the client API functions. //* Implementation of the client API functions.
{% for type in by_category["object"] %} {% for type in by_category["object"] %}
{% set Type = type.name.CamelCase() %} {% set Type = type.name.CamelCase() %}
@ -305,8 +299,7 @@ namespace dawn_wire {
self->builderCallback.canCall = false; self->builderCallback.canCall = false;
{% endif %} {% endif %}
cmd.resultId = allocation->object->id; cmd.result = ObjectHandle{allocation->object->id, allocation->serial};
cmd.resultSerial = allocation->serial;
{% endif %} {% endif %}
{% for arg in method.arguments %} {% for arg in method.arguments %}
@ -353,8 +346,9 @@ namespace dawn_wire {
cmd.objectType = ObjectType::{{type.name.CamelCase()}}; cmd.objectType = ObjectType::{{type.name.CamelCase()}};
cmd.objectId = obj->id; cmd.objectId = obj->id;
auto allocCmd = static_cast<decltype(cmd)*>(obj->device->GetCmdSpace(sizeof(cmd))); size_t requiredSize = cmd.GetRequiredSize();
*allocCmd = cmd; char* allocatedBuffer = static_cast<char*>(obj->device->GetCmdSpace(requiredSize));
cmd.Serialize(allocatedBuffer);
obj->device->{{type.name.camelCase()}}.Free(obj); obj->device->{{type.name.camelCase()}}.Free(obj);
} }
@ -386,8 +380,9 @@ namespace dawn_wire {
cmd.size = size; cmd.size = size;
cmd.isWrite = false; cmd.isWrite = false;
auto allocCmd = static_cast<decltype(cmd)*>(buffer->device->GetCmdSpace(sizeof(cmd))); size_t requiredSize = cmd.GetRequiredSize();
*allocCmd = cmd; char* allocatedBuffer = static_cast<char*>(buffer->device->GetCmdSpace(requiredSize));
cmd.Serialize(allocatedBuffer);
} }
void ClientBufferMapWriteAsync(dawnBuffer cBuffer, uint32_t start, uint32_t size, dawnBufferMapWriteCallback callback, dawnCallbackUserdata userdata) { void ClientBufferMapWriteAsync(dawnBuffer cBuffer, uint32_t start, uint32_t size, dawnBufferMapWriteCallback callback, dawnCallbackUserdata userdata) {
@ -410,8 +405,9 @@ namespace dawn_wire {
cmd.size = size; cmd.size = size;
cmd.isWrite = true; cmd.isWrite = true;
auto allocCmd = static_cast<decltype(cmd)*>(buffer->device->GetCmdSpace(sizeof(cmd))); size_t requiredSize = cmd.GetRequiredSize();
*allocCmd = cmd; char* allocatedBuffer = static_cast<char*>(buffer->device->GetCmdSpace(requiredSize));
cmd.Serialize(allocatedBuffer);
} }
uint64_t ClientFenceGetCompletedValue(dawnFence cSelf) { uint64_t ClientFenceGetCompletedValue(dawnFence cSelf) {
@ -458,12 +454,11 @@ namespace dawn_wire {
BufferUpdateMappedDataCmd cmd; BufferUpdateMappedDataCmd cmd;
cmd.bufferId = buffer->id; cmd.bufferId = buffer->id;
cmd.dataLength = static_cast<uint32_t>(buffer->mappedDataSize); cmd.dataLength = static_cast<uint32_t>(buffer->mappedDataSize);
cmd.data = reinterpret_cast<const uint8_t*>(buffer->mappedData);
auto allocCmd = static_cast<decltype(cmd)*>(buffer->device->GetCmdSpace(sizeof(cmd))); size_t requiredSize = cmd.GetRequiredSize();
*allocCmd = cmd; char* allocatedBuffer = static_cast<char*>(buffer->device->GetCmdSpace(requiredSize));
cmd.Serialize(allocatedBuffer);
void* dataAlloc = buffer->device->GetCmdSpace(cmd.dataLength);
memcpy(dataAlloc, buffer->mappedData, cmd.dataLength);
} }
free(buffer->mappedData); free(buffer->mappedData);
@ -510,14 +505,12 @@ namespace dawn_wire {
// - An autogenerated Client{{suffix}} method that sends the command on the wire // - An autogenerated Client{{suffix}} method that sends the command on the wire
// - A manual ProxyClient{{suffix}} method that will be inserted in the proctable instead of // - A manual ProxyClient{{suffix}} method that will be inserted in the proctable instead of
// the autogenerated one, and that will have to call Client{{suffix}} // the autogenerated one, and that will have to call Client{{suffix}}
{% set proxied_commands = ["BufferUnmap", "DeviceCreateFence", "QueueSignal"] %}
dawnProcTable GetProcs() { dawnProcTable GetProcs() {
dawnProcTable table; dawnProcTable table;
{% for type in by_category["object"] %} {% for type in by_category["object"] %}
{% for method in native_methods(type) %} {% for method in native_methods(type) %}
{% set suffix = as_MethodSuffix(type.name, method.name) %} {% set suffix = as_MethodSuffix(type.name, method.name) %}
{% if suffix in proxied_commands %} {% if suffix in client_proxied_commands %}
table.{{as_varName(type.name, method.name)}} = ProxyClient{{suffix}}; table.{{as_varName(type.name, method.name)}} = ProxyClient{{suffix}};
{% else %} {% else %}
table.{{as_varName(type.name, method.name)}} = Client{{suffix}}; table.{{as_varName(type.name, method.name)}} = Client{{suffix}};
@ -538,23 +531,11 @@ namespace dawn_wire {
bool success = false; bool success = false;
switch (cmdId) { switch (cmdId) {
case ReturnWireCmd::DeviceErrorCallback: {% for command in cmd_records["return command"] %}
success = HandleDeviceErrorCallbackCmd(&commands, &size); case ReturnWireCmd::{{command.name.CamelCase()}}:
break; success = Handle{{command.name.CamelCase()}}(&commands, &size);
{% for type in by_category["object"] if type.is_builder %}
case ReturnWireCmd::{{type.name.CamelCase()}}ErrorCallback:
success = Handle{{type.name.CamelCase()}}ErrorCallbackCmd(&commands, &size);
break; break;
{% endfor %} {% endfor %}
case ReturnWireCmd::BufferMapReadAsyncCallback:
success = HandleBufferMapReadAsyncCallback(&commands, &size);
break;
case ReturnWireCmd::BufferMapWriteAsyncCallback:
success = HandleBufferMapWriteAsyncCallback(&commands, &size);
break;
case ReturnWireCmd::FenceUpdateCompletedValue:
success = HandleFenceUpdateCompletedValue(&commands, &size);
break;
default: default:
success = false; success = false;
} }
@ -562,6 +543,7 @@ namespace dawn_wire {
if (!success) { if (!success) {
return nullptr; return nullptr;
} }
mAllocator.Reset();
} }
if (size != 0) { if (size != 0) {
@ -573,72 +555,47 @@ namespace dawn_wire {
private: private:
Device* mDevice = nullptr; Device* mDevice = nullptr;
WireDeserializeAllocator mAllocator;
//* Helper function for the getting of the command data in command handlers. bool HandleDeviceErrorCallback(const char** commands, size_t* size) {
//* Checks there is enough data left, updates the buffer / size and returns ReturnDeviceErrorCallbackCmd cmd;
//* the command (or nullptr for an error). DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
template <typename T>
static const T* GetData(const char** buffer, size_t* size, size_t count) {
// TODO(cwallez@chromium.org): Check for overflow
size_t totalSize = count * sizeof(T);
if (*size < totalSize) {
return nullptr;
}
const T* data = reinterpret_cast<const T*>(*buffer); if (deserializeResult == DeserializeResult::FatalError) {
*buffer += totalSize;
*size -= totalSize;
return data;
}
template <typename T>
static const T* GetCommand(const char** commands, size_t* size) {
return GetData<T>(commands, size, 1);
}
bool HandleDeviceErrorCallbackCmd(const char** commands, size_t* size) {
const auto* cmd = GetCommand<ReturnDeviceErrorCallbackCmd>(commands, size);
if (cmd == nullptr) {
return false; return false;
} }
const char* message = GetData<char>(commands, size, cmd->messageStrlen + 1); DAWN_ASSERT(cmd.message != nullptr);
if (message == nullptr || message[cmd->messageStrlen] != '\0') { mDevice->HandleError(cmd.message);
return false;
}
mDevice->HandleError(message);
return true; return true;
} }
{% for type in by_category["object"] if type.is_builder %} {% for type in by_category["object"] if type.is_builder %}
{% set Type = type.name.CamelCase() %} {% set Type = type.name.CamelCase() %}
bool Handle{{Type}}ErrorCallbackCmd(const char** commands, size_t* size) { bool Handle{{Type}}ErrorCallback(const char** commands, size_t* size) {
const auto* cmd = GetCommand<Return{{Type}}ErrorCallbackCmd>(commands, size); Return{{Type}}ErrorCallbackCmd cmd;
if (cmd == nullptr) { DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
if (deserializeResult == DeserializeResult::FatalError) {
return false; return false;
} }
const char* message = GetData<char>(commands, size, cmd->messageStrlen + 1); DAWN_ASSERT(cmd.message != nullptr);
if (message == nullptr || message[cmd->messageStrlen] != '\0') {
return false;
}
auto* builtObject = mDevice->{{type.built_type.name.camelCase()}}.GetObject(cmd->builtObjectId); auto* builtObject = mDevice->{{type.built_type.name.camelCase()}}.GetObject(cmd.builtObject.id);
uint32_t objectSerial = mDevice->{{type.built_type.name.camelCase()}}.GetSerial(cmd->builtObjectId); uint32_t objectSerial = mDevice->{{type.built_type.name.camelCase()}}.GetSerial(cmd.builtObject.id);
//* The object might have been deleted or a new object created with the same ID. //* The object might have been deleted or a new object created with the same ID.
if (builtObject == nullptr || objectSerial != cmd->builtObjectSerial) { if (builtObject == nullptr || objectSerial != cmd.builtObject.serial) {
return true; return true;
} }
bool called = builtObject->builderCallback.Call(static_cast<dawnBuilderErrorStatus>(cmd->status), message); bool called = builtObject->builderCallback.Call(static_cast<dawnBuilderErrorStatus>(cmd.status), cmd.message);
// Unhandled builder errors are forwarded to the device // Unhandled builder errors are forwarded to the device
if (!called && cmd->status != DAWN_BUILDER_ERROR_STATUS_SUCCESS && cmd->status != DAWN_BUILDER_ERROR_STATUS_UNKNOWN) { if (!called && cmd.status != DAWN_BUILDER_ERROR_STATUS_SUCCESS && cmd.status != DAWN_BUILDER_ERROR_STATUS_UNKNOWN) {
mDevice->HandleError(("Unhandled builder error: " + std::string(message)).c_str()); mDevice->HandleError(("Unhandled builder error: " + std::string(cmd.message)).c_str());
} }
return true; return true;
@ -646,31 +603,23 @@ namespace dawn_wire {
{% endfor %} {% endfor %}
bool HandleBufferMapReadAsyncCallback(const char** commands, size_t* size) { bool HandleBufferMapReadAsyncCallback(const char** commands, size_t* size) {
const auto* cmd = GetCommand<ReturnBufferMapReadAsyncCallbackCmd>(commands, size); ReturnBufferMapReadAsyncCallbackCmd cmd;
if (cmd == nullptr) { DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
if (deserializeResult == DeserializeResult::FatalError) {
return false; return false;
} }
//* Unconditionnally get the data from the buffer so that the correct amount of data is auto* buffer = mDevice->buffer.GetObject(cmd.buffer.id);
//* consumed from the buffer, even when we ignore the command and early out. uint32_t bufferSerial = mDevice->buffer.GetSerial(cmd.buffer.id);
const char* requestData = nullptr;
if (cmd->status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
requestData = GetData<char>(commands, size, cmd->dataLength);
if (requestData == nullptr) {
return false;
}
}
auto* buffer = mDevice->buffer.GetObject(cmd->bufferId);
uint32_t bufferSerial = mDevice->buffer.GetSerial(cmd->bufferId);
//* The buffer might have been deleted or recreated so this isn't an error. //* The buffer might have been deleted or recreated so this isn't an error.
if (buffer == nullptr || bufferSerial != cmd->bufferSerial) { if (buffer == nullptr || bufferSerial != cmd.buffer.serial) {
return true; return true;
} }
//* The requests can have been deleted via an Unmap so this isn't an error. //* The requests can have been deleted via an Unmap so this isn't an error.
auto requestIt = buffer->requests.find(cmd->requestSerial); auto requestIt = buffer->requests.find(cmd.requestSerial);
if (requestIt == buffer->requests.end()) { if (requestIt == buffer->requests.end()) {
return true; return true;
} }
@ -686,14 +635,14 @@ namespace dawn_wire {
buffer->requests.erase(requestIt); buffer->requests.erase(requestIt);
//* On success, we copy the data locally because the IPC buffer isn't valid outside of this function //* On success, we copy the data locally because the IPC buffer isn't valid outside of this function
if (cmd->status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { if (cmd.status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
//* The server didn't send the right amount of data, this is an error and could cause //* The server didn't send the right amount of data, this is an error and could cause
//* the application to crash if we did call the callback. //* the application to crash if we did call the callback.
if (request.size != cmd->dataLength) { if (request.size != cmd.dataLength) {
return false; return false;
} }
ASSERT(requestData != nullptr); ASSERT(cmd.data != nullptr);
if (buffer->mappedData != nullptr) { if (buffer->mappedData != nullptr) {
return false; return false;
@ -702,32 +651,34 @@ namespace dawn_wire {
buffer->isWriteMapped = false; buffer->isWriteMapped = false;
buffer->mappedDataSize = request.size; buffer->mappedDataSize = request.size;
buffer->mappedData = malloc(request.size); buffer->mappedData = malloc(request.size);
memcpy(buffer->mappedData, requestData, request.size); memcpy(buffer->mappedData, cmd.data, request.size);
request.readCallback(static_cast<dawnBufferMapAsyncStatus>(cmd->status), buffer->mappedData, request.userdata); request.readCallback(static_cast<dawnBufferMapAsyncStatus>(cmd.status), buffer->mappedData, request.userdata);
} else { } else {
request.readCallback(static_cast<dawnBufferMapAsyncStatus>(cmd->status), nullptr, request.userdata); request.readCallback(static_cast<dawnBufferMapAsyncStatus>(cmd.status), nullptr, request.userdata);
} }
return true; return true;
} }
bool HandleBufferMapWriteAsyncCallback(const char** commands, size_t* size) { bool HandleBufferMapWriteAsyncCallback(const char** commands, size_t* size) {
const auto* cmd = GetCommand<ReturnBufferMapWriteAsyncCallbackCmd>(commands, size); ReturnBufferMapWriteAsyncCallbackCmd cmd;
if (cmd == nullptr) { DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
if (deserializeResult == DeserializeResult::FatalError) {
return false; return false;
} }
auto* buffer = mDevice->buffer.GetObject(cmd->bufferId); auto* buffer = mDevice->buffer.GetObject(cmd.buffer.id);
uint32_t bufferSerial = mDevice->buffer.GetSerial(cmd->bufferId); uint32_t bufferSerial = mDevice->buffer.GetSerial(cmd.buffer.id);
//* The buffer might have been deleted or recreated so this isn't an error. //* The buffer might have been deleted or recreated so this isn't an error.
if (buffer == nullptr || bufferSerial != cmd->bufferSerial) { if (buffer == nullptr || bufferSerial != cmd.buffer.serial) {
return true; return true;
} }
//* The requests can have been deleted via an Unmap so this isn't an error. //* The requests can have been deleted via an Unmap so this isn't an error.
auto requestIt = buffer->requests.find(cmd->requestSerial); auto requestIt = buffer->requests.find(cmd.requestSerial);
if (requestIt == buffer->requests.end()) { if (requestIt == buffer->requests.end()) {
return true; return true;
} }
@ -742,7 +693,7 @@ namespace dawn_wire {
buffer->requests.erase(requestIt); buffer->requests.erase(requestIt);
//* On success, we copy the data locally because the IPC buffer isn't valid outside of this function //* On success, we copy the data locally because the IPC buffer isn't valid outside of this function
if (cmd->status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { if (cmd.status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
if (buffer->mappedData != nullptr) { if (buffer->mappedData != nullptr) {
return false; return false;
} }
@ -752,29 +703,31 @@ namespace dawn_wire {
buffer->mappedData = malloc(request.size); buffer->mappedData = malloc(request.size);
memset(buffer->mappedData, 0, request.size); memset(buffer->mappedData, 0, request.size);
request.writeCallback(static_cast<dawnBufferMapAsyncStatus>(cmd->status), buffer->mappedData, request.userdata); request.writeCallback(static_cast<dawnBufferMapAsyncStatus>(cmd.status), buffer->mappedData, request.userdata);
} else { } else {
request.writeCallback(static_cast<dawnBufferMapAsyncStatus>(cmd->status), nullptr, request.userdata); request.writeCallback(static_cast<dawnBufferMapAsyncStatus>(cmd.status), nullptr, request.userdata);
} }
return true; return true;
} }
bool HandleFenceUpdateCompletedValue(const char** commands, size_t* size) { bool HandleFenceUpdateCompletedValue(const char** commands, size_t* size) {
const auto* cmd = GetCommand<ReturnFenceUpdateCompletedValueCmd>(commands, size); ReturnFenceUpdateCompletedValueCmd cmd;
if (cmd == nullptr) { DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
if (deserializeResult == DeserializeResult::FatalError) {
return false; return false;
} }
auto* fence = mDevice->fence.GetObject(cmd->fenceId); auto* fence = mDevice->fence.GetObject(cmd.fence.id);
uint32_t fenceSerial = mDevice->fence.GetSerial(cmd->fenceId); uint32_t fenceSerial = mDevice->fence.GetSerial(cmd.fence.id);
//* The fence might have been deleted or recreated so this isn't an error. //* The fence might have been deleted or recreated so this isn't an error.
if (fence == nullptr || fenceSerial != cmd->fenceSerial) { if (fence == nullptr || fenceSerial != cmd.fence.serial) {
return true; return true;
} }
fence->completedValue = cmd->value; fence->completedValue = cmd.value;
fence->CheckPassedFences(); fence->CheckPassedFences();
return true; return true;
} }

View File

@ -12,7 +12,7 @@
//* See the License for the specific language governing permissions and //* See the License for the specific language governing permissions and
//* limitations under the License. //* limitations under the License.
#include "dawn_wire/WireCmd.h" #include "dawn_wire/WireCmd_autogen.h"
#include "common/Assert.h" #include "common/Assert.h"
@ -49,10 +49,14 @@
//* Outputs the serialization code to put `in` in `out` //* Outputs the serialization code to put `in` in `out`
{% macro serialize_member(member, in, out) %} {% macro serialize_member(member, in, out) %}
{%- if member.type.category == "object" -%} {%- if member.type.category == "object" -%}
{% set Optional = "Optional" if member.optional else "" %} {%- set Optional = "Optional" if member.optional else "" -%}
{{out}} = provider.Get{{Optional}}Id({{in}}); {{out}} = provider.Get{{Optional}}Id({{in}});
{% elif member.type.category == "structure"%} {%- elif member.type.category == "structure" -%}
{{as_cType(member.type.name)}}Serialize({{in}}, &{{out}}, buffer, provider); {{as_cType(member.type.name)}}Serialize({{in}}, &{{out}}, buffer
{%- if member.type.has_dawn_object -%}
, provider
{%- endif -%}
);
{%- else -%} {%- else -%}
{{out}} = {{in}}; {{out}} = {{in}};
{%- endif -%} {%- endif -%}
@ -61,42 +65,34 @@
//* Outputs the deserialization code to put `in` in `out` //* Outputs the deserialization code to put `in` in `out`
{% macro deserialize_member(member, in, out) %} {% macro deserialize_member(member, in, out) %}
{%- if member.type.category == "object" -%} {%- if member.type.category == "object" -%}
{% set Optional = "Optional" if member.optional else "" %} {%- set Optional = "Optional" if member.optional else "" -%}
DESERIALIZE_TRY(resolver.Get{{Optional}}FromId({{in}}, &{{out}})); DESERIALIZE_TRY(resolver.Get{{Optional}}FromId({{in}}, &{{out}}));
{% elif member.type.category == "structure"%} {%- elif member.type.category == "structure" -%}
DESERIALIZE_TRY({{as_cType(member.type.name)}}Deserialize(&{{out}}, &{{in}}, buffer, size, allocator, resolver)); DESERIALIZE_TRY({{as_cType(member.type.name)}}Deserialize(&{{out}}, &{{in}}, buffer, size, allocator
{%- if member.type.has_dawn_object -%}
, resolver
{%- endif -%}
));
{%- else -%} {%- else -%}
{{out}} = {{in}}; {{out}} = {{in}};
{%- endif -%} {%- endif -%}
{% endmacro %} {% endmacro %}
//* The main [de]serialization macro //* The main [de]serialization macro
//* Methods are very similar to structures that have one member corresponding to each arguments. //* Methods are very similar to structures that have one member corresponding to each arguments.
//* This macro takes advantage of the similarity to output [de]serialization code for a record //* This macro takes advantage of the similarity to output [de]serialization code for a record
//* that is either a structure or a method, with some special cases for each. //* that is either a structure or a method, with some special cases for each.
{% macro write_serialization_methods(name, members, as_method=None, as_struct=None, is_return_command=False) %} {% macro write_record_serialization_helpers(record, name, members, is_cmd=False, is_method=False, is_return_command=False) %}
{% set Return = "Return" if is_return_command else "" %} {% set Return = "Return" if is_return_command else "" %}
{% set is_method = as_method != None %} {% set Cmd = "Cmd" if is_cmd else "" %}
{% set is_struct = as_struct != None %}
//* Structure for the wire format of each of the records. Members that are values //* Structure for the wire format of each of the records. Members that are values
//* are embedded directly in the structure. Other members are assumed to be in the //* are embedded directly in the structure. Other members are assumed to be in the
//* memory directly following the structure in the buffer. //* memory directly following the structure in the buffer.
struct {{name}}Transfer { struct {{Return}}{{name}}Transfer {
{% if is_method %} {% if is_cmd %}
//* Start the transfer structure with the command ID, so that casting to WireCmd gives the ID. //* Start the transfer structure with the command ID, so that casting to WireCmd gives the ID.
{{Return}}WireCmd commandId; {{Return}}WireCmd commandId;
//* Methods always have an implicit "self" argument.
ObjectId self;
//* Methods that return objects need to declare to the server which ID will be used for the
//* return value.
{% if as_method.return_type.category == "object" %}
ObjectId resultId;
ObjectSerial resultSerial;
{% endif %}
{% endif %} {% endif %}
//* Value types are directly in the command, objects being replaced with their IDs. //* Value types are directly in the command, objects being replaced with their IDs.
@ -111,7 +107,7 @@
}; };
//* Returns the required transfer size for `record` in addition to the transfer structure. //* Returns the required transfer size for `record` in addition to the transfer structure.
DAWN_DECLARE_UNUSED size_t {{name}}GetExtraRequiredSize(const {{name}}& record) { DAWN_DECLARE_UNUSED size_t {{Return}}{{name}}GetExtraRequiredSize(const {{Return}}{{name}}{{Cmd}}& record) {
DAWN_UNUSED(record); DAWN_UNUSED(record);
size_t result = 0; size_t result = 0;
@ -140,24 +136,21 @@
} }
// GetExtraRequiredSize isn't used for structures that are value members of other structures // GetExtraRequiredSize isn't used for structures that are value members of other structures
// because we assume they cannot contain pointers themselves. // because we assume they cannot contain pointers themselves.
DAWN_UNUSED_FUNC({{name}}GetExtraRequiredSize); DAWN_UNUSED_FUNC({{Return}}{{name}}GetExtraRequiredSize);
//* Serializes `record` into `transfer`, using `buffer` to get more space for pointed-to data //* Serializes `record` into `transfer`, using `buffer` to get more space for pointed-to data
//* and `provider` to serialize objects. //* and `provider` to serialize objects.
void {{name}}Serialize(const {{name}}& record, {{name}}Transfer* transfer, void {{Return}}{{name}}Serialize(const {{Return}}{{name}}{{Cmd}}& record, {{Return}}{{name}}Transfer* transfer,
char** buffer, const ObjectIdProvider& provider) { char** buffer
DAWN_UNUSED(provider); {%- if record.has_dawn_object -%}
, const ObjectIdProvider& provider
{%- endif -%}
) {
DAWN_UNUSED(buffer); DAWN_UNUSED(buffer);
//* Handle special transfer members of methods. //* Handle special transfer members of methods.
{% if is_method %} {% if is_cmd %}
{% if as_method.return_type.category == "object" %}
transfer->resultId = record.resultId;
transfer->resultSerial = record.resultSerial;
{% endif %}
transfer->commandId = {{Return}}WireCmd::{{name}}; transfer->commandId = {{Return}}WireCmd::{{name}};
transfer->self = provider.GetId(record.self);
{% endif %} {% endif %}
//* Value types are directly in the transfer record, objects being replaced with their IDs. //* Value types are directly in the transfer record, objects being replaced with their IDs.
@ -193,30 +186,56 @@
//* Deserializes `transfer` into `record` getting more serialized data from `buffer` and `size` //* Deserializes `transfer` into `record` getting more serialized data from `buffer` and `size`
//* if needed, using `allocator` to store pointed-to values and `resolver` to translate object //* if needed, using `allocator` to store pointed-to values and `resolver` to translate object
//* Ids to actual objects. //* Ids to actual objects.
DeserializeResult {{name}}Deserialize({{name}}* record, const {{name}}Transfer* transfer, DeserializeResult {{Return}}{{name}}Deserialize({{Return}}{{name}}{{Cmd}}* record, const {{Return}}{{name}}Transfer* transfer,
const char** buffer, size_t* size, DeserializeAllocator* allocator, const ObjectIdResolver& resolver) { const char** buffer, size_t* size, DeserializeAllocator* allocator
{%- if record.has_dawn_object -%}
, const ObjectIdResolver& resolver
{%- endif -%}
) {
DAWN_UNUSED(allocator); DAWN_UNUSED(allocator);
DAWN_UNUSED(resolver);
DAWN_UNUSED(buffer); DAWN_UNUSED(buffer);
DAWN_UNUSED(size); DAWN_UNUSED(size);
{% if is_cmd %}
ASSERT(transfer->commandId == {{Return}}WireCmd::{{name}});
{% endif %}
//* First assign result ObjectHandles:
//* Deserialize guarantees they are filled even if there is an ID for an error object
//* for the Maybe monad mechanism.
//* TODO(enga): This won't need to be done first once we have "WebGPU error handling".
{% set return_handles = members
|selectattr("is_return_value")
|selectattr("annotation", "equalto", "value")
|selectattr("type.dict_name", "equalto", "ObjectHandle")
|list %}
//* Strip return_handles so we don't deserialize it again
{% set members = members|reject("in", return_handles)|list %}
{% for member in return_handles %}
{% set memberName = as_varName(member.name) %}
{{deserialize_member(member, "transfer->" + memberName, "record->" + memberName)}}
{% endfor %}
//* Handle special transfer members for methods //* Handle special transfer members for methods
{% if is_method %} {% if is_method %}
{% if as_method.return_type.category == "object" %} //* First assign selfId:
record->resultId = transfer->resultId; //* Deserialize guarantees they are filled even if there is an ID for an error object
record->resultSerial = transfer->resultSerial; //* for the Maybe monad mechanism.
{% endif %} //* TODO(enga): This won't need to be done first once we have "WebGPU error handling".
//* We can also remove is_method
ASSERT(transfer->commandId == {{Return}}WireCmd::{{name}});
record->selfId = transfer->self; record->selfId = transfer->self;
//* This conversion is done after the copying of result* and selfId: Deserialize //* This conversion is done after the copying of result* and selfId: Deserialize
//* guarantees they are filled even if there is an ID for an error object for the //* guarantees they are filled even if there is an ID for an error object for the
//* Maybe monad mechanism. //* Maybe monad mechanism.
DESERIALIZE_TRY(resolver.GetFromId(record->selfId, &record->self)); DESERIALIZE_TRY(resolver.GetFromId(record->selfId, &record->self));
//* Strip self so we don't deserialize it again
{% set members = members|rejectattr("name.chunks", "equalto", ["self"])|list %}
//* The object resolver returns a success even if the object is null because the //* The object resolver returns a success even if the object is null because the
//* frontend is reponsible to validate that (null objects sometimes have special //* frontend is responsible to validate that (null objects sometimes have special
//* meanings). However it is never valid to call a method on a null object so we //* meanings). However it is never valid to call a method on a null object so we
//* can error out in that case. //* can error out in that case.
if (record->self == nullptr) { if (record->self == nullptr) {
@ -224,7 +243,7 @@
} }
{% endif %} {% endif %}
{% if is_struct and as_struct.extensible %} {% if record.extensible %}
record->nextInChain = nullptr; record->nextInChain = nullptr;
{% endif %} {% endif %}
@ -272,6 +291,47 @@
} }
{% endmacro %} {% endmacro %}
{% macro write_command_serialization_methods(command, is_return) %}
{% set Return = "Return" if is_return else "" %}
{% set Name = Return + command.name.CamelCase() %}
{% set Cmd = Name + "Cmd" %}
size_t {{Cmd}}::GetRequiredSize() const {
size_t size = sizeof({{Name}}Transfer) + {{Name}}GetExtraRequiredSize(*this);
return size;
}
void {{Cmd}}::Serialize(char* buffer
{%- if command.has_dawn_object -%}
, const ObjectIdProvider& objectIdProvider
{%- endif -%}
) const {
auto transfer = reinterpret_cast<{{Name}}Transfer*>(buffer);
buffer += sizeof({{Name}}Transfer);
{{Name}}Serialize(*this, transfer, &buffer
{%- if command.has_dawn_object -%}
, objectIdProvider
{%- endif -%}
);
}
DeserializeResult {{Cmd}}::Deserialize(const char** buffer, size_t* size, DeserializeAllocator* allocator
{%- if command.has_dawn_object -%}
, const ObjectIdResolver& resolver
{%- endif -%}
) {
const {{Name}}Transfer* transfer = nullptr;
DESERIALIZE_TRY(GetPtrFromBuffer(buffer, size, 1, &transfer));
return {{Name}}Deserialize(this, transfer, buffer, size, allocator
{%- if command.has_dawn_object -%}
, resolver
{%- endif -%}
);
}
{% endmacro %}
namespace dawn_wire { namespace dawn_wire {
// Macro to simplify error handling, similar to DAWN_TRY but for DeserializeResult. // Macro to simplify error handling, similar to DAWN_TRY but for DeserializeResult.
@ -324,46 +384,35 @@ namespace dawn_wire {
return DeserializeResult::Success; return DeserializeResult::Success;
} }
//* Output structure [de]serialization first because it is used by methods. //* Output structure [de]serialization first because it is used by commands.
{% for type in by_category["structure"] %} {% for type in by_category["structure"] %}
{% set name = as_cType(type.name) %} {% set name = as_cType(type.name) %}
{{write_serialization_methods(name, type.members, as_struct=type)}} {{write_record_serialization_helpers(type, name, type.members,
is_cmd=False)}}
{% endfor %} {% endfor %}
// * Output [de]serialization helpers for methods //* Output [de]serialization helpers for commands
{% for type in by_category["object"] %} {% for command in cmd_records["command"] %}
{% for method in type.methods %} {% set name = command.name.CamelCase() %}
{% set name = as_MethodSuffix(type.name, method.name) %} {{write_record_serialization_helpers(command, name, command.members,
is_cmd=True, is_method=command.derived_method != None)}}
{% endfor %}
using {{name}} = {{name}}Cmd; //* Output [de]serialization helpers for return commands
{{write_serialization_methods(name, method.arguments, as_method=method)}} {% for command in cmd_records["return command"] %}
{% endfor %} {% set name = command.name.CamelCase() %}
{{write_record_serialization_helpers(command, name, command.members,
is_cmd=True, is_method=command.derived_method != None,
is_return_command=True)}}
{% endfor %} {% endfor %}
} // anonymous namespace } // anonymous namespace
{% for type in by_category["object"] %} {% for command in cmd_records["command"] %}
{% for method in type.methods %} {{ write_command_serialization_methods(command, False) }}
{% set name = as_MethodSuffix(type.name, method.name) %} {% endfor %}
{% set Cmd = name + "Cmd" %}
size_t {{Cmd}}::GetRequiredSize() const { {% for command in cmd_records["return command"] %}
return sizeof({{name}}Transfer) + {{name}}GetExtraRequiredSize(*this); {{ write_command_serialization_methods(command, True) }}
}
void {{Cmd}}::Serialize(char* buffer, const ObjectIdProvider& objectIdProvider) const {
auto transfer = reinterpret_cast<{{name}}Transfer*>(buffer);
buffer += sizeof({{name}}Transfer);
{{name}}Serialize(*this, transfer, &buffer, objectIdProvider);
}
DeserializeResult {{Cmd}}::Deserialize(const char** buffer, size_t* size, DeserializeAllocator* allocator, const ObjectIdResolver& resolver) {
const {{name}}Transfer* transfer = nullptr;
DESERIALIZE_TRY(GetPtrFromBuffer(buffer, size, 1, &transfer));
return {{name}}Deserialize(this, transfer, buffer, size, allocator, resolver);
}
{% endfor %}
{% endfor %} {% endfor %}
} // namespace dawn_wire } // namespace dawn_wire

View File

@ -15,10 +15,16 @@
#ifndef DAWNWIRE_WIRECMD_AUTOGEN_H_ #ifndef DAWNWIRE_WIRECMD_AUTOGEN_H_
#define DAWNWIRE_WIRECMD_AUTOGEN_H_ #define DAWNWIRE_WIRECMD_AUTOGEN_H_
#include <dawn/dawn.h>
namespace dawn_wire { namespace dawn_wire {
using ObjectId = uint32_t; using ObjectId = uint32_t;
using ObjectSerial = uint32_t; using ObjectSerial = uint32_t;
struct ObjectHandle {
ObjectId id;
ObjectSerial serial;
};
enum class DeserializeResult { enum class DeserializeResult {
Success, Success,
@ -61,84 +67,68 @@ namespace dawn_wire {
//* Enum used as a prefix to each command on the wire format. //* Enum used as a prefix to each command on the wire format.
enum class WireCmd : uint32_t { enum class WireCmd : uint32_t {
{% for type in by_category["object"] %} {% for command in cmd_records["command"] %}
{% for method in type.methods %} {{command.name.CamelCase()}},
{{as_MethodSuffix(type.name, method.name)}},
{% endfor %}
{% endfor %} {% endfor %}
BufferMapAsync,
BufferUpdateMappedDataCmd,
DestroyObject,
}; };
{% for type in by_category["object"] %}
{% for method in type.methods %}
{% set Suffix = as_MethodSuffix(type.name, method.name) %}
{% set Cmd = Suffix + "Cmd" %}
//* These are "structure" version of the list of arguments to the different Dawn methods.
//* They provide helpers to serialize/deserialize to/from a buffer.
struct {{Cmd}} {
//* From a filled structure, compute how much size will be used in the serialization buffer.
size_t GetRequiredSize() const;
//* Serialize the structure and everything it points to into serializeBuffer which must be
//* big enough to contain all the data (as queried from GetRequiredSize).
void Serialize(char* serializeBuffer, const ObjectIdProvider& objectIdProvider) const;
//* Deserializes the structure from a buffer, consuming a maximum of *size bytes. When this
//* function returns, buffer and size will be updated by the number of bytes consumed to
//* deserialize the structure. Structures containing pointers will use allocator to get
//* scratch space to deserialize the pointed-to data.
//* Deserialize returns:
//* - Success if everything went well (yay!)
//* - FatalError is something bad happened (buffer too small for example)
//* - ErrorObject if one if the deserialized object is an error value, for the implementation
//* of the Maybe monad.
//* If the return value is not FatalError, selfId, resultId and resultSerial (if present) are
//* filled.
DeserializeResult Deserialize(const char** buffer, size_t* size, DeserializeAllocator* allocator, const ObjectIdResolver& resolver);
{{as_cType(type.name)}} self;
//* Command handlers want to know the object ID in addition to the backing object.
//* Doesn't need to be filled before Serialize, or GetRequiredSize.
ObjectId selfId;
//* Commands creating objects say which ID the created object will be referred as.
{% if method.return_type.category == "object" %}
ObjectId resultId;
ObjectSerial resultSerial;
{% endif %}
{% for arg in method.arguments %}
{{as_annotated_cType(arg)}};
{% endfor %}
};
{% endfor %}
{% endfor %}
//* Enum used as a prefix to each command on the return wire format. //* Enum used as a prefix to each command on the return wire format.
enum class ReturnWireCmd : uint32_t { enum class ReturnWireCmd : uint32_t {
DeviceErrorCallback, {% for command in cmd_records["return command"] %}
{% for type in by_category["object"] if type.is_builder %} {{command.name.CamelCase()}},
{{type.name.CamelCase()}}ErrorCallback,
{% endfor %} {% endfor %}
BufferMapReadAsyncCallback,
BufferMapWriteAsyncCallback,
FenceUpdateCompletedValue,
}; };
//* Command for the server calling a builder status callback. {% macro write_command_struct(command, is_return_command) %}
{% for type in by_category["object"] if type.is_builder %} {% set Return = "Return" if is_return_command else "" %}
struct Return{{type.name.CamelCase()}}ErrorCallbackCmd { {% set Cmd = command.name.CamelCase() + "Cmd" %}
ReturnWireCmd commandId = ReturnWireCmd::{{type.name.CamelCase()}}ErrorCallback; struct {{Return}}{{Cmd}} {
//* From a filled structure, compute how much size will be used in the serialization buffer.
size_t GetRequiredSize() const;
ObjectId builtObjectId; //* Serialize the structure and everything it points to into serializeBuffer which must be
ObjectSerial builtObjectSerial; //* big enough to contain all the data (as queried from GetRequiredSize).
uint32_t status; void Serialize(char* serializeBuffer
size_t messageStrlen; {%- if command.has_dawn_object -%}
}; , const ObjectIdProvider& objectIdProvider
{%- endif -%}
) const;
//* Deserializes the structure from a buffer, consuming a maximum of *size bytes. When this
//* function returns, buffer and size will be updated by the number of bytes consumed to
//* deserialize the structure. Structures containing pointers will use allocator to get
//* scratch space to deserialize the pointed-to data.
//* Deserialize returns:
//* - Success if everything went well (yay!)
//* - FatalError is something bad happened (buffer too small for example)
//* - ErrorObject if one if the deserialized object is an error value, for the implementation
//* of the Maybe monad.
//* If the return value is not FatalError, selfId, resultId and resultSerial (if present) are
//* filled.
DeserializeResult Deserialize(const char** buffer, size_t* size, DeserializeAllocator* allocator
{%- if command.has_dawn_object -%}
, const ObjectIdResolver& resolver
{%- endif -%}
);
{% if command.derived_method %}
//* Command handlers want to know the object ID in addition to the backing object.
//* Doesn't need to be filled before Serialize, or GetRequiredSize.
ObjectId selfId;
{% endif %}
{% for member in command.members %}
{{as_annotated_cType(member)}};
{% endfor %}
};
{% endmacro %}
{% for command in cmd_records["command"] %}
{{write_command_struct(command, False)}}
{% endfor %}
{% for command in cmd_records["return command"] %}
{{write_command_struct(command, True)}}
{% endfor %} {% endfor %}
} // namespace dawn_wire } // namespace dawn_wire

View File

@ -14,7 +14,8 @@
#include "dawn_wire/TypeTraits_autogen.h" #include "dawn_wire/TypeTraits_autogen.h"
#include "dawn_wire/Wire.h" #include "dawn_wire/Wire.h"
#include "dawn_wire/WireCmd.h" #include "dawn_wire/WireCmd_autogen.h"
#include "dawn_wire/WireDeserializeAllocator.h"
#include "common/Assert.h" #include "common/Assert.h"
@ -32,8 +33,7 @@ namespace dawn_wire {
struct MapUserdata { struct MapUserdata {
Server* server; Server* server;
uint32_t bufferId; ObjectHandle buffer;
uint32_t bufferSerial;
uint32_t requestSerial; uint32_t requestSerial;
uint32_t size; uint32_t size;
bool isWrite; bool isWrite;
@ -41,8 +41,7 @@ namespace dawn_wire {
struct FenceCompletionUserdata { struct FenceCompletionUserdata {
Server* server; Server* server;
uint32_t fenceId; ObjectHandle fence;
uint32_t fenceSerial;
uint64_t value; uint64_t value;
}; };
@ -69,8 +68,7 @@ namespace dawn_wire {
template <typename T> template <typename T>
struct ObjectData<T, true> : public ObjectDataBase<T> { struct ObjectData<T, true> : public ObjectDataBase<T> {
uint32_t builtObjectId = 0; ObjectHandle builtObject = ObjectHandle{0, 0};
uint32_t builtObjectSerial = 0;
}; };
template <> template <>
@ -214,59 +212,6 @@ namespace dawn_wire {
void ForwardFenceCompletedValue(dawnFenceCompletionStatus status, void ForwardFenceCompletedValue(dawnFenceCompletionStatus status,
dawnCallbackUserdata userdata); dawnCallbackUserdata userdata);
// A really really simple implementation of the DeserializeAllocator. It's main feature
// is that it has some inline storage so as to avoid allocations for the majority of
// commands.
class ServerAllocator : public DeserializeAllocator {
public:
ServerAllocator() {
Reset();
}
~ServerAllocator() {
Reset();
}
void* GetSpace(size_t size) override {
// Return space in the current buffer if possible first.
if (mRemainingSize >= size) {
char* buffer = mCurrentBuffer;
mCurrentBuffer += size;
mRemainingSize -= size;
return buffer;
}
// Otherwise allocate a new buffer and try again.
size_t allocationSize = std::max(size, size_t(2048));
char* allocation = static_cast<char*>(malloc(allocationSize));
if (allocation == nullptr) {
return nullptr;
}
mAllocations.push_back(allocation);
mCurrentBuffer = allocation;
mRemainingSize = allocationSize;
return GetSpace(size);
}
void Reset() {
for (auto allocation : mAllocations) {
free(allocation);
}
mAllocations.clear();
// The initial buffer is the inline buffer so that some allocations can be skipped
mCurrentBuffer = mStaticBuffer;
mRemainingSize = sizeof(mStaticBuffer);
}
private:
size_t mRemainingSize = 0;
char* mCurrentBuffer = nullptr;
char mStaticBuffer[2048];
std::vector<char*> mAllocations;
};
class Server : public CommandHandler, public ObjectIdResolver { class Server : public CommandHandler, public ObjectIdResolver {
public: public:
Server(dawnDevice device, const dawnProcTable& procs, CommandSerializer* serializer) Server(dawnDevice device, const dawnProcTable& procs, CommandSerializer* serializer)
@ -294,13 +239,11 @@ namespace dawn_wire {
void OnDeviceError(const char* message) { void OnDeviceError(const char* message) {
ReturnDeviceErrorCallbackCmd cmd; ReturnDeviceErrorCallbackCmd cmd;
cmd.messageStrlen = std::strlen(message); cmd.message = message;
auto allocCmd = static_cast<ReturnDeviceErrorCallbackCmd*>(GetCmdSpace(sizeof(cmd))); size_t requiredSize = cmd.GetRequiredSize();
*allocCmd = cmd; char* allocatedBuffer = static_cast<char*>(GetCmdSpace(requiredSize));
cmd.Serialize(allocatedBuffer);
char* messageAlloc = static_cast<char*>(GetCmdSpace(cmd.messageStrlen + 1));
strcpy(messageAlloc, message);
} }
{% for type in by_category["object"] if type.is_builder%} {% for type in by_category["object"] if type.is_builder%}
@ -319,18 +262,16 @@ namespace dawn_wire {
if (status != DAWN_BUILDER_ERROR_STATUS_UNKNOWN) { if (status != DAWN_BUILDER_ERROR_STATUS_UNKNOWN) {
//* Unknown is the only status that can be returned without a call to GetResult //* Unknown is the only status that can be returned without a call to GetResult
//* so we are guaranteed to have created an object. //* so we are guaranteed to have created an object.
ASSERT(builder->builtObjectId != 0); ASSERT(builder->builtObject.id != 0);
Return{{Type}}ErrorCallbackCmd cmd; Return{{Type}}ErrorCallbackCmd cmd;
cmd.builtObjectId = builder->builtObjectId; cmd.builtObject = builder->builtObject;
cmd.builtObjectSerial = builder->builtObjectSerial;
cmd.status = status; cmd.status = status;
cmd.messageStrlen = std::strlen(message); cmd.message = message;
auto allocCmd = static_cast<Return{{Type}}ErrorCallbackCmd*>(GetCmdSpace(sizeof(cmd))); size_t requiredSize = cmd.GetRequiredSize();
*allocCmd = cmd; char* allocatedBuffer = static_cast<char*>(GetCmdSpace(requiredSize));
char* messageAlloc = static_cast<char*>(GetCmdSpace(strlen(message) + 1)); cmd.Serialize(allocatedBuffer);
strcpy(messageAlloc, message);
} }
} }
{% endfor %} {% endfor %}
@ -339,46 +280,44 @@ namespace dawn_wire {
std::unique_ptr<MapUserdata> data(userdata); std::unique_ptr<MapUserdata> data(userdata);
// Skip sending the callback if the buffer has already been destroyed. // Skip sending the callback if the buffer has already been destroyed.
auto* bufferData = mKnownBuffer.Get(data->bufferId); auto* bufferData = mKnownBuffer.Get(data->buffer.id);
if (bufferData == nullptr || bufferData->serial != data->bufferSerial) { if (bufferData == nullptr || bufferData->serial != data->buffer.serial) {
return; return;
} }
ReturnBufferMapReadAsyncCallbackCmd cmd; ReturnBufferMapReadAsyncCallbackCmd cmd;
cmd.bufferId = data->bufferId; cmd.buffer = data->buffer;
cmd.bufferSerial = data->bufferSerial;
cmd.requestSerial = data->requestSerial; cmd.requestSerial = data->requestSerial;
cmd.status = status; cmd.status = status;
cmd.dataLength = 0; cmd.dataLength = 0;
cmd.data = reinterpret_cast<const uint8_t*>(ptr);
auto allocCmd = static_cast<ReturnBufferMapReadAsyncCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
*allocCmd = cmd;
if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
allocCmd->dataLength = data->size; cmd.dataLength = data->size;
void* dataAlloc = GetCmdSpace(data->size);
memcpy(dataAlloc, ptr, data->size);
} }
size_t requiredSize = cmd.GetRequiredSize();
char* allocatedBuffer = static_cast<char*>(GetCmdSpace(requiredSize));
cmd.Serialize(allocatedBuffer);
} }
void OnMapWriteAsyncCallback(dawnBufferMapAsyncStatus status, void* ptr, MapUserdata* userdata) { void OnMapWriteAsyncCallback(dawnBufferMapAsyncStatus status, void* ptr, MapUserdata* userdata) {
std::unique_ptr<MapUserdata> data(userdata); std::unique_ptr<MapUserdata> data(userdata);
// Skip sending the callback if the buffer has already been destroyed. // Skip sending the callback if the buffer has already been destroyed.
auto* bufferData = mKnownBuffer.Get(data->bufferId); auto* bufferData = mKnownBuffer.Get(data->buffer.id);
if (bufferData == nullptr || bufferData->serial != data->bufferSerial) { if (bufferData == nullptr || bufferData->serial != data->buffer.serial) {
return; return;
} }
ReturnBufferMapWriteAsyncCallbackCmd cmd; ReturnBufferMapWriteAsyncCallbackCmd cmd;
cmd.bufferId = data->bufferId; cmd.buffer = data->buffer;
cmd.bufferSerial = data->bufferSerial;
cmd.requestSerial = data->requestSerial; cmd.requestSerial = data->requestSerial;
cmd.status = status; cmd.status = status;
auto allocCmd = static_cast<ReturnBufferMapWriteAsyncCallbackCmd*>(GetCmdSpace(sizeof(cmd))); size_t requiredSize = cmd.GetRequiredSize();
*allocCmd = cmd; char* allocatedBuffer = static_cast<char*>(GetCmdSpace(requiredSize));
cmd.Serialize(allocatedBuffer);
if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
bufferData->mappedData = ptr; bufferData->mappedData = ptr;
@ -390,15 +329,14 @@ namespace dawn_wire {
std::unique_ptr<FenceCompletionUserdata> data(userdata); std::unique_ptr<FenceCompletionUserdata> data(userdata);
ReturnFenceUpdateCompletedValueCmd cmd; ReturnFenceUpdateCompletedValueCmd cmd;
cmd.fenceId = data->fenceId; cmd.fence = data->fence;
cmd.fenceSerial = data->fenceSerial;
cmd.value = data->value; cmd.value = data->value;
auto allocCmd = static_cast<ReturnFenceUpdateCompletedValueCmd*>(GetCmdSpace(sizeof(cmd))); size_t requiredSize = cmd.GetRequiredSize();
*allocCmd = cmd; char* allocatedBuffer = static_cast<char*>(GetCmdSpace(requiredSize));
cmd.Serialize(allocatedBuffer);
} }
{% set client_side_commands = ["FenceGetCompletedValue"] %}
const char* HandleCommands(const char* commands, size_t size) override { const char* HandleCommands(const char* commands, size_t size) override {
mProcs.deviceTick(mKnownDevice.Get(1)->handle); mProcs.deviceTick(mKnownDevice.Get(1)->handle);
@ -407,25 +345,11 @@ namespace dawn_wire {
bool success = false; bool success = false;
switch (cmdId) { switch (cmdId) {
{% for type in by_category["object"] %} {% for command in cmd_records["command"] %}
{% for method in type.methods %} case WireCmd::{{command.name.CamelCase()}}:
{% set Suffix = as_MethodSuffix(type.name, method.name) %} success = Handle{{command.name.CamelCase()}}(&commands, &size);
{% if Suffix not in client_side_commands %} break;
case WireCmd::{{Suffix}}:
success = Handle{{Suffix}}(&commands, &size);
break;
{% endif %}
{% endfor %}
{% endfor %} {% endfor %}
case WireCmd::BufferMapAsync:
success = HandleBufferMapAsync(&commands, &size);
break;
case WireCmd::BufferUpdateMappedDataCmd:
success = HandleBufferUpdateMappedData(&commands, &size);
break;
case WireCmd::DestroyObject:
success = HandleDestroyObject(&commands, &size);
break;
default: default:
success = false; success = false;
} }
@ -447,7 +371,7 @@ namespace dawn_wire {
dawnProcTable mProcs; dawnProcTable mProcs;
CommandSerializer* mSerializer = nullptr; CommandSerializer* mSerializer = nullptr;
ServerAllocator mAllocator; WireDeserializeAllocator mAllocator;
void* GetCmdSpace(size_t size) { void* GetCmdSpace(size_t size) {
return mSerializer->GetCmdSpace(size); return mSerializer->GetCmdSpace(size);
@ -484,36 +408,10 @@ namespace dawn_wire {
KnownObjects<{{as_cType(type.name)}}> mKnown{{type.name.CamelCase()}}; KnownObjects<{{as_cType(type.name)}}> mKnown{{type.name.CamelCase()}};
{% endfor %} {% endfor %}
{% set reverse_lookup_object_types = ["Fence"] %} {% for type in by_category["object"] if type.name.CamelCase() in server_reverse_lookup_objects %}
{% for type in by_category["object"] if type.name.CamelCase() in reverse_lookup_object_types %}
ObjectIdLookupTable<{{as_cType(type.name)}}> m{{type.name.CamelCase()}}IdTable; ObjectIdLookupTable<{{as_cType(type.name)}}> m{{type.name.CamelCase()}}IdTable;
{% endfor %} {% endfor %}
//* Helper function for the getting of the command data in command handlers.
//* Checks there is enough data left, updates the buffer / size and returns
//* the command (or nullptr for an error).
template <typename T>
static const T* GetData(const char** buffer, size_t* size, size_t count) {
// TODO(cwallez@chromium.org): Check for overflow
size_t totalSize = count * sizeof(T);
if (*size < totalSize) {
return nullptr;
}
const T* data = reinterpret_cast<const T*>(*buffer);
*buffer += totalSize;
*size -= totalSize;
return data;
}
template <typename T>
static const T* GetCommand(const char** commands, size_t* size) {
return GetData<T>(commands, size, 1);
}
{% set custom_pre_handler_commands = ["BufferUnmap"] %}
bool PreHandleBufferUnmap(const BufferUnmapCmd& cmd) { bool PreHandleBufferUnmap(const BufferUnmapCmd& cmd) {
auto* selfData = mKnownBuffer.Get(cmd.selfId); auto* selfData = mKnownBuffer.Get(cmd.selfId);
ASSERT(selfData != nullptr); ASSERT(selfData != nullptr);
@ -523,8 +421,6 @@ namespace dawn_wire {
return true; return true;
} }
{% set custom_post_handler_commands = ["QueueSignal"] %}
bool PostHandleQueueSignal(const QueueSignalCmd& cmd) { bool PostHandleQueueSignal(const QueueSignalCmd& cmd) {
if (cmd.fence == nullptr) { if (cmd.fence == nullptr) {
return false; return false;
@ -536,8 +432,7 @@ namespace dawn_wire {
auto* data = new FenceCompletionUserdata; auto* data = new FenceCompletionUserdata;
data->server = this; data->server = this;
data->fenceId = fenceId; data->fence = ObjectHandle{fenceId, fence->serial};
data->fenceSerial = fence->serial;
data->value = cmd.signalValue; data->value = cmd.signalValue;
auto userdata = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data)); auto userdata = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data));
@ -560,7 +455,7 @@ namespace dawn_wire {
return false; return false;
} }
{% if Suffix in custom_pre_handler_commands %} {% if Suffix in server_custom_pre_handler_commands %}
if (!PreHandle{{Suffix}}(cmd)) { if (!PreHandle{{Suffix}}(cmd)) {
return false; return false;
} }
@ -575,15 +470,14 @@ namespace dawn_wire {
{% set returns = return_type.name.canonical_case() != "void" %} {% set returns = return_type.name.canonical_case() != "void" %}
{% if returns %} {% if returns %}
{% set Type = method.return_type.name.CamelCase() %} {% set Type = method.return_type.name.CamelCase() %}
auto* resultData = mKnown{{Type}}.Allocate(cmd.resultId); auto* resultData = mKnown{{Type}}.Allocate(cmd.result.id);
if (resultData == nullptr) { if (resultData == nullptr) {
return false; return false;
} }
resultData->serial = cmd.resultSerial; resultData->serial = cmd.result.serial;
{% if type.is_builder %} {% if type.is_builder %}
selfData->builtObjectId = cmd.resultId; selfData->builtObject = cmd.result;
selfData->builtObjectSerial = cmd.resultSerial;
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -608,7 +502,7 @@ namespace dawn_wire {
{%- endfor -%} {%- endfor -%}
); );
{% if Suffix in custom_post_handler_commands %} {% if Suffix in server_custom_post_handler_commands %}
if (!PostHandle{{Suffix}}(cmd)) { if (!PostHandle{{Suffix}}(cmd)) {
return false; return false;
} }
@ -618,10 +512,10 @@ namespace dawn_wire {
resultData->handle = result; resultData->handle = result;
resultData->valid = result != nullptr; resultData->valid = result != nullptr;
{% if return_type.name.CamelCase() in reverse_lookup_object_types %} {% if return_type.name.CamelCase() in server_reverse_lookup_objects %}
//* For created objects, store a mapping from them back to their client IDs //* For created objects, store a mapping from them back to their client IDs
if (result) { if (result) {
m{{return_type.name.CamelCase()}}IdTable.Store(result, cmd.resultId); m{{return_type.name.CamelCase()}}IdTable.Store(result, cmd.result.id);
} }
{% endif %} {% endif %}
@ -630,7 +524,7 @@ namespace dawn_wire {
{% if return_type.is_builder %} {% if return_type.is_builder %}
if (result != nullptr) { if (result != nullptr) {
uint64_t userdata1 = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this)); uint64_t userdata1 = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this));
uint64_t userdata2 = (uint64_t(resultData->serial) << uint64_t(32)) + cmd.resultId; uint64_t userdata2 = (uint64_t(resultData->serial) << uint64_t(32)) + cmd.result.id;
mProcs.{{as_varName(return_type.name, Name("set error callback"))}}(result, Forward{{return_type.name.CamelCase()}}ToClient, userdata1, userdata2); mProcs.{{as_varName(return_type.name, Name("set error callback"))}}(result, Forward{{return_type.name.CamelCase()}}ToClient, userdata1, userdata2);
} }
{% endif %} {% endif %}
@ -645,16 +539,18 @@ namespace dawn_wire {
bool HandleBufferMapAsync(const char** commands, size_t* size) { bool HandleBufferMapAsync(const char** commands, size_t* size) {
//* These requests are just forwarded to the buffer, with userdata containing what the client //* These requests are just forwarded to the buffer, with userdata containing what the client
//* will require in the return command. //* will require in the return command.
const auto* cmd = GetCommand<BufferMapAsyncCmd>(commands, size); BufferMapAsyncCmd cmd;
if (cmd == nullptr) { DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
if (deserializeResult == DeserializeResult::FatalError) {
return false; return false;
} }
ObjectId bufferId = cmd->bufferId; ObjectId bufferId = cmd.bufferId;
uint32_t requestSerial = cmd->requestSerial; uint32_t requestSerial = cmd.requestSerial;
uint32_t requestSize = cmd->size; uint32_t requestSize = cmd.size;
uint32_t requestStart = cmd->start; uint32_t requestStart = cmd.start;
bool isWrite = cmd->isWrite; bool isWrite = cmd.isWrite;
//* The null object isn't valid as `self` //* The null object isn't valid as `self`
if (bufferId == 0) { if (bufferId == 0) {
@ -668,8 +564,7 @@ namespace dawn_wire {
auto* data = new MapUserdata; auto* data = new MapUserdata;
data->server = this; data->server = this;
data->bufferId = bufferId; data->buffer = ObjectHandle{bufferId, buffer->serial};
data->bufferSerial = buffer->serial;
data->requestSerial = requestSerial; data->requestSerial = requestSerial;
data->size = requestSize; data->size = requestSize;
data->isWrite = isWrite; data->isWrite = isWrite;
@ -696,13 +591,15 @@ namespace dawn_wire {
} }
bool HandleBufferUpdateMappedData(const char** commands, size_t* size) { bool HandleBufferUpdateMappedData(const char** commands, size_t* size) {
const auto* cmd = GetCommand<BufferUpdateMappedDataCmd>(commands, size); BufferUpdateMappedDataCmd cmd;
if (cmd == nullptr) { DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
if (deserializeResult == DeserializeResult::FatalError) {
return false; return false;
} }
ObjectId bufferId = cmd->bufferId; ObjectId bufferId = cmd.bufferId;
size_t dataLength = cmd->dataLength; size_t dataLength = cmd.dataLength;
//* The null object isn't valid as `self` //* The null object isn't valid as `self`
if (bufferId == 0) { if (bufferId == 0) {
@ -715,29 +612,28 @@ namespace dawn_wire {
return false; return false;
} }
const char* data = GetData<char>(commands, size, dataLength); DAWN_ASSERT(cmd.data != nullptr);
if (data == nullptr) {
return false;
}
memcpy(buffer->mappedData, data, dataLength); memcpy(buffer->mappedData, cmd.data, dataLength);
return true; return true;
} }
bool HandleDestroyObject(const char** commands, size_t* size) { bool HandleDestroyObject(const char** commands, size_t* size) {
const auto* cmd = GetCommand<DestroyObjectCmd>(commands, size); DestroyObjectCmd cmd;
if (cmd == nullptr) { DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
if (deserializeResult == DeserializeResult::FatalError) {
return false; return false;
} }
ObjectId objectId = cmd->objectId; ObjectId objectId = cmd.objectId;
//* ID 0 are reserved for nullptr and cannot be destroyed. //* ID 0 are reserved for nullptr and cannot be destroyed.
if (objectId == 0) { if (objectId == 0) {
return false; return false;
} }
switch (cmd->objectType) { switch (cmd.objectType) {
{% for type in by_category["object"] %} {% for type in by_category["object"] %}
{% set ObjectType = type.name.CamelCase() %} {% set ObjectType = type.name.CamelCase() %}
case ObjectType::{{ObjectType}}: { case ObjectType::{{ObjectType}}: {
@ -749,7 +645,7 @@ namespace dawn_wire {
if (data == nullptr) { if (data == nullptr) {
return false; return false;
} }
{% if type.name.CamelCase() in reverse_lookup_object_types %} {% if type.name.CamelCase() in server_reverse_lookup_objects %}
m{{type.name.CamelCase()}}IdTable.Remove(data->handle); m{{type.name.CamelCase()}}IdTable.Remove(data->handle);
{% endif %} {% endif %}

93
generator/wire_cmd.py Normal file
View File

@ -0,0 +1,93 @@
# Copyright 2019 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.
from collections import namedtuple
from common import Name
import common
def concat_names(*names):
return ' '.join([name.canonical_case() for name in names])
# 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 = []
object_result_member = common.RecordMember(Name('result'), types['ObjectHandle'], 'value', False, True)
string_message_member = common.RecordMember(Name('message'), types['char'], 'const*', False, False)
string_message_member.length = 'strlen'
built_object_member = common.RecordMember(Name('built object'), types['ObjectHandle'], 'value', False, False)
callback_status_member = common.RecordMember(Name('status'), types['uint32_t'], 'value', False, False)
# Generate commands from object methods
for api_object in wire_params['by_category']['object']:
for method in api_object.methods:
if method.return_type.category != 'object' and method.return_type.name.canonical_case() != 'void':
# No other return types supported
continue
# Create object method commands by prepending "self"
members = [common.RecordMember(Name('self'), types[api_object.dict_name], 'value', False, False)]
members += method.arguments
# Client->Server commands that return an object return the result object handle
if method.return_type.category == 'object':
members.append(object_result_member)
command_name = concat_names(api_object.name, method.name)
command = common.Command(command_name, members)
command.derived_object = api_object
command.derived_method = method
commands.append(command)
# Create builder return ErrorCallback commands
# This can be removed when WebGPU error handling is implemented
if api_object.is_builder:
command_name = concat_names(api_object.name, Name('error callback'))
command = common.Command(command_name, [
built_object_member,
callback_status_member,
string_message_member,
])
command.derived_object = api_object
return_commands.append(command)
for (name, json_data) in wire_json['commands'].items():
commands.append(common.Command(name, common.linked_record_members(json_data, types)))
for (name, json_data) in wire_json['return commands'].items():
return_commands.append(common.Command(name, common.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

View File

@ -1,83 +0,0 @@
// 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.
#ifndef DAWNWIRE_WIRECMD_H_
#define DAWNWIRE_WIRECMD_H_
#include <dawn/dawn.h>
#include "dawn_wire/WireCmd_autogen.h"
namespace dawn_wire {
struct ReturnDeviceErrorCallbackCmd {
ReturnWireCmd commandId = ReturnWireCmd::DeviceErrorCallback;
size_t messageStrlen;
};
struct BufferMapAsyncCmd {
WireCmd commandId = WireCmd::BufferMapAsync;
ObjectId bufferId;
ObjectSerial requestSerial;
uint32_t start;
uint32_t size;
bool isWrite;
};
struct ReturnBufferMapReadAsyncCallbackCmd {
ReturnWireCmd commandId = ReturnWireCmd::BufferMapReadAsyncCallback;
ObjectId bufferId;
ObjectSerial bufferSerial;
uint32_t requestSerial;
uint32_t status;
uint32_t dataLength;
};
struct ReturnBufferMapWriteAsyncCallbackCmd {
ReturnWireCmd commandId = ReturnWireCmd::BufferMapWriteAsyncCallback;
ObjectId bufferId;
ObjectSerial bufferSerial;
uint32_t requestSerial;
uint32_t status;
};
struct ReturnFenceUpdateCompletedValueCmd {
ReturnWireCmd commandId = ReturnWireCmd::FenceUpdateCompletedValue;
ObjectId fenceId;
ObjectSerial fenceSerial;
uint64_t value;
};
struct BufferUpdateMappedDataCmd {
WireCmd commandId = WireCmd::BufferUpdateMappedDataCmd;
ObjectId bufferId;
uint32_t dataLength;
};
struct DestroyObjectCmd {
WireCmd commandId = WireCmd::DestroyObject;
ObjectType objectType;
ObjectId objectId;
};
} // namespace dawn_wire
#endif // DAWNWIRE_WIRECMD_H_

View File

@ -0,0 +1,60 @@
// Copyright 2019 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.
#include "dawn_wire/WireDeserializeAllocator.h"
#include <algorithm>
namespace dawn_wire {
WireDeserializeAllocator::WireDeserializeAllocator() {
Reset();
}
WireDeserializeAllocator::~WireDeserializeAllocator() {
Reset();
}
void* WireDeserializeAllocator::GetSpace(size_t size) {
// Return space in the current buffer if possible first.
if (mRemainingSize >= size) {
char* buffer = mCurrentBuffer;
mCurrentBuffer += size;
mRemainingSize -= size;
return buffer;
}
// Otherwise allocate a new buffer and try again.
size_t allocationSize = std::max(size, size_t(2048));
char* allocation = static_cast<char*>(malloc(allocationSize));
if (allocation == nullptr) {
return nullptr;
}
mAllocations.push_back(allocation);
mCurrentBuffer = allocation;
mRemainingSize = allocationSize;
return GetSpace(size);
}
void WireDeserializeAllocator::Reset() {
for (auto allocation : mAllocations) {
free(allocation);
}
mAllocations.clear();
// The initial buffer is the inline buffer so that some allocations can be skipped
mCurrentBuffer = mStaticBuffer;
mRemainingSize = sizeof(mStaticBuffer);
}
} // namespace dawn_wire

View File

@ -0,0 +1,43 @@
// Copyright 2019 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.
#ifndef DAWNWIRE_WIREDESERIALIZEALLOCATOR_H_
#define DAWNWIRE_WIREDESERIALIZEALLOCATOR_H_
#include "dawn_wire/WireCmd_autogen.h"
#include <vector>
namespace dawn_wire {
// A really really simple implementation of the DeserializeAllocator. It's main feature
// is that it has some inline storage so as to avoid allocations for the majority of
// commands.
class WireDeserializeAllocator : public DeserializeAllocator {
public:
WireDeserializeAllocator();
~WireDeserializeAllocator();
void* GetSpace(size_t size) override;
void Reset();
private:
size_t mRemainingSize = 0;
char* mCurrentBuffer = nullptr;
char mStaticBuffer[2048];
std::vector<char*> mAllocations;
};
} // namespace dawn_wire
#endif // DAWNWIRE_WIREDESERIALIZEALLOCATOR_H_