diff --git a/.gitignore b/.gitignore index 5ab63d20fd..1dc05c780c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*.pyc + # Directories added by gclient sync and the GN build .gclient .gclient_entries diff --git a/BUILD.gn b/BUILD.gn index 5d61bc18ff..91b64f5afe 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -54,6 +54,8 @@ template("dawn_generator") { # target using templates in this directory. generator_args = [ rebase_path("dawn.json", root_build_dir), + "--wire-json", + rebase_path("dawn_wire.json", root_build_dir), "--template-dir", rebase_path("generator/templates", root_build_dir), "--targets", @@ -695,8 +697,8 @@ dawn_static_and_shared_library("libdawn_native") { ] deps = [ - ":libdawn_native_sources", ":dawn_common", + ":libdawn_native_sources", ] sources = [ "src/dawn_native/DawnNative.cpp", @@ -761,7 +763,10 @@ dawn_static_and_shared_library("libdawn_wire") { configs = [ ":dawn_internal" ] 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 public_deps = [ @@ -849,8 +854,8 @@ test("dawn_unittests") { ":dawn_headers", ":dawn_utils", ":libdawn", - ":libdawn_native_sources", ":libdawn_native", + ":libdawn_native_sources", ":libdawn_wire", ":mock_dawn_gen", "third_party:gmock_and_gtest", diff --git a/dawn.json b/dawn.json index 44b9814c1f..6848bd8f02 100644 --- a/dawn.json +++ b/dawn.json @@ -1106,6 +1106,18 @@ {"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": { "category": "native" }, diff --git a/dawn_wire.json b/dawn_wire.json new file mode 100644 index 0000000000..eaa93b2d12 --- /dev/null +++ b/dawn_wire.json @@ -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" + ] + } +} diff --git a/generator/common.py b/generator/common.py new file mode 100644 index 0000000000..977dee9622 --- /dev/null +++ b/generator/common.py @@ -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 diff --git a/generator/main.py b/generator/main.py index a5c5a7d114..32cf516009 100644 --- a/generator/main.py +++ b/generator/main.py @@ -17,90 +17,9 @@ # COMMON ############################################################ 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): - 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 = [] +from common import Name +import common +import wire_cmd ############################################################ # PARSE @@ -111,35 +30,10 @@ def is_native_method(method): return method.return_type.category == "natively defined" or \ any([arg.type.category == "natively defined" for arg in method.arguments]) -def 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 make_method(json_data): - arguments = linked_record_members(json_data.get('args', []), types) - return Method(Name(json_data['name']), types[json_data.get('returns', 'void')], arguments) + arguments = common.linked_record_members(json_data.get('args', []), types) + return common.Method(Name(json_data['name']), types[json_data.get('returns', 'void')], arguments) methods = [make_method(m) for m in obj.json_data.get('methods', [])] obj.methods = [method for method in methods if not is_native_method(method)] @@ -154,7 +48,7 @@ def link_object(obj, types): assert(obj.built_type != None) 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 # 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): category_to_parser = { - 'bitmask': BitmaskType, - 'enum': EnumType, - 'native': NativeType, - 'natively defined': NativelyDefined, - 'object': ObjectType, - 'structure': StructureType, + 'bitmask': common.BitmaskType, + 'enum': common.EnumType, + 'native': common.NativeType, + 'natively defined': common.NativelyDefined, + 'object': common.ObjectType, + 'structure': common.StructureType, } types = {} @@ -227,6 +121,9 @@ def parse_json(json): by_category['structure'] = topo_sort_structure(by_category['structure']) + for struct in by_category['structure']: + struct.update_metadata() + return { 'types': types, 'by_category': by_category @@ -392,18 +289,18 @@ def cpp_native_methods(types, typ): methods = typ.methods + typ.native_methods if typ.is_builder: - methods.append(Method(Name('set error callback'), types['void'], [ - RecordMember(Name('callback'), types['builder error callback'], 'value', False), - RecordMember(Name('userdata1'), types['callback userdata'], 'value', False), - RecordMember(Name('userdata2'), types['callback userdata'], 'value', False), + methods.append(common.Method(Name('set error callback'), types['void'], [ + common.RecordMember(Name('callback'), types['builder error callback'], 'value', False, False), + common.RecordMember(Name('userdata1'), types['callback userdata'], 'value', False, False), + common.RecordMember(Name('userdata2'), types['callback userdata'], 'value', False, False), ])) return methods def c_native_methods(types, typ): return cpp_native_methods(types, typ) + [ - Method(Name('reference'), types['void'], []), - Method(Name('release'), types['void'], []), + common.Method(Name('reference'), types['void'], []), + common.Method(Name('release'), types['void'], []), ] def js_native_methods(types, typ): @@ -412,7 +309,7 @@ def js_native_methods(types, typ): def debug(text): print(text) -def get_renders_for_targets(api_params, targets): +def get_renders_for_targets(api_params, wire_json, targets): base_params = { 'enumerate': enumerate, '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)) if 'dawn_wire' in targets: + additional_params = wire_cmd.compute_wire_params(api_params, wire_json) + wire_params = [ base_params, api_params, c_params, { 'as_wireType': lambda typ: typ.name.CamelCase() + '*' if typ.category == 'object' else as_cppType(typ.name) - } + }, + additional_params ] - renders.append(FileRender('dawn_wire/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.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/WireServer.cpp', 'dawn_wire/WireServer.cpp', wire_params)) @@ -507,6 +407,7 @@ def main(): 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('--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', '--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') @@ -522,7 +423,17 @@ def main(): api_params = parse_json(loaded_json) 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. # Load the file and compare with our renders. @@ -544,8 +455,9 @@ def main(): if args.output_json_tarball != None: 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(os.path.join(os.path.abspath(os.path.dirname(__file__)), "wire_cmd.py")) output_depfile(args.depfile, args.output_json_tarball, dependencies) if __name__ == '__main__': diff --git a/generator/templates/dawn_wire/WireClient.cpp b/generator/templates/dawn_wire/WireClient.cpp index ce708e4eee..9f8dd4918d 100644 --- a/generator/templates/dawn_wire/WireClient.cpp +++ b/generator/templates/dawn_wire/WireClient.cpp @@ -13,7 +13,8 @@ //* limitations under the License. #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/SerialMap.h" @@ -66,12 +67,7 @@ namespace dawn_wire { BuilderCallbackData builderCallback; }; - {% set special_objects = [ - "device", - "buffer", - "fence", - ] %} - {% for type in by_category["object"] if not type.name.canonical_case() in special_objects %} + {% for type in by_category["object"] if not type.name.CamelCase() in client_special_objects %} struct {{type.name.CamelCase()}} : ObjectBase { using ObjectBase::ObjectBase; }; @@ -269,8 +265,6 @@ namespace dawn_wire { CommandSerializer* mSerializer = nullptr; }; - {% set client_side_commands = ["FenceGetCompletedValue"] %} - //* Implementation of the client API functions. {% for type in by_category["object"] %} {% set Type = type.name.CamelCase() %} @@ -305,8 +299,7 @@ namespace dawn_wire { self->builderCallback.canCall = false; {% endif %} - cmd.resultId = allocation->object->id; - cmd.resultSerial = allocation->serial; + cmd.result = ObjectHandle{allocation->object->id, allocation->serial}; {% endif %} {% for arg in method.arguments %} @@ -353,8 +346,9 @@ namespace dawn_wire { cmd.objectType = ObjectType::{{type.name.CamelCase()}}; cmd.objectId = obj->id; - auto allocCmd = static_cast(obj->device->GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(obj->device->GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); obj->device->{{type.name.camelCase()}}.Free(obj); } @@ -386,8 +380,9 @@ namespace dawn_wire { cmd.size = size; cmd.isWrite = false; - auto allocCmd = static_cast(buffer->device->GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(buffer->device->GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); } 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.isWrite = true; - auto allocCmd = static_cast(buffer->device->GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(buffer->device->GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); } uint64_t ClientFenceGetCompletedValue(dawnFence cSelf) { @@ -458,12 +454,11 @@ namespace dawn_wire { BufferUpdateMappedDataCmd cmd; cmd.bufferId = buffer->id; cmd.dataLength = static_cast(buffer->mappedDataSize); + cmd.data = reinterpret_cast(buffer->mappedData); - auto allocCmd = static_cast(buffer->device->GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; - - void* dataAlloc = buffer->device->GetCmdSpace(cmd.dataLength); - memcpy(dataAlloc, buffer->mappedData, cmd.dataLength); + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(buffer->device->GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); } free(buffer->mappedData); @@ -510,14 +505,12 @@ namespace dawn_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 // the autogenerated one, and that will have to call Client{{suffix}} - {% set proxied_commands = ["BufferUnmap", "DeviceCreateFence", "QueueSignal"] %} - dawnProcTable GetProcs() { dawnProcTable table; {% for type in by_category["object"] %} {% for method in native_methods(type) %} {% 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}}; {% else %} table.{{as_varName(type.name, method.name)}} = Client{{suffix}}; @@ -538,23 +531,11 @@ namespace dawn_wire { bool success = false; switch (cmdId) { - case ReturnWireCmd::DeviceErrorCallback: - success = HandleDeviceErrorCallbackCmd(&commands, &size); - break; - {% for type in by_category["object"] if type.is_builder %} - case ReturnWireCmd::{{type.name.CamelCase()}}ErrorCallback: - success = Handle{{type.name.CamelCase()}}ErrorCallbackCmd(&commands, &size); + {% for command in cmd_records["return command"] %} + case ReturnWireCmd::{{command.name.CamelCase()}}: + success = Handle{{command.name.CamelCase()}}(&commands, &size); break; {% 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: success = false; } @@ -562,6 +543,7 @@ namespace dawn_wire { if (!success) { return nullptr; } + mAllocator.Reset(); } if (size != 0) { @@ -573,72 +555,47 @@ namespace dawn_wire { private: Device* mDevice = nullptr; + WireDeserializeAllocator mAllocator; - //* 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 - 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; - } + bool HandleDeviceErrorCallback(const char** commands, size_t* size) { + ReturnDeviceErrorCallbackCmd cmd; + DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator); - const T* data = reinterpret_cast(*buffer); - - *buffer += totalSize; - *size -= totalSize; - - return data; - } - template - static const T* GetCommand(const char** commands, size_t* size) { - return GetData(commands, size, 1); - } - - bool HandleDeviceErrorCallbackCmd(const char** commands, size_t* size) { - const auto* cmd = GetCommand(commands, size); - if (cmd == nullptr) { + if (deserializeResult == DeserializeResult::FatalError) { return false; } - const char* message = GetData(commands, size, cmd->messageStrlen + 1); - if (message == nullptr || message[cmd->messageStrlen] != '\0') { - return false; - } - - mDevice->HandleError(message); + DAWN_ASSERT(cmd.message != nullptr); + mDevice->HandleError(cmd.message); return true; } {% for type in by_category["object"] if type.is_builder %} {% set Type = type.name.CamelCase() %} - bool Handle{{Type}}ErrorCallbackCmd(const char** commands, size_t* size) { - const auto* cmd = GetCommand(commands, size); - if (cmd == nullptr) { + bool Handle{{Type}}ErrorCallback(const char** commands, size_t* size) { + Return{{Type}}ErrorCallbackCmd cmd; + DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator); + + if (deserializeResult == DeserializeResult::FatalError) { return false; } - const char* message = GetData(commands, size, cmd->messageStrlen + 1); - if (message == nullptr || message[cmd->messageStrlen] != '\0') { - return false; - } + DAWN_ASSERT(cmd.message != nullptr); - auto* builtObject = mDevice->{{type.built_type.name.camelCase()}}.GetObject(cmd->builtObjectId); - uint32_t objectSerial = mDevice->{{type.built_type.name.camelCase()}}.GetSerial(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.builtObject.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; } - bool called = builtObject->builderCallback.Call(static_cast(cmd->status), message); + bool called = builtObject->builderCallback.Call(static_cast(cmd.status), cmd.message); // Unhandled builder errors are forwarded to the device - 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()); + if (!called && cmd.status != DAWN_BUILDER_ERROR_STATUS_SUCCESS && cmd.status != DAWN_BUILDER_ERROR_STATUS_UNKNOWN) { + mDevice->HandleError(("Unhandled builder error: " + std::string(cmd.message)).c_str()); } return true; @@ -646,31 +603,23 @@ namespace dawn_wire { {% endfor %} bool HandleBufferMapReadAsyncCallback(const char** commands, size_t* size) { - const auto* cmd = GetCommand(commands, size); - if (cmd == nullptr) { + ReturnBufferMapReadAsyncCallbackCmd cmd; + DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator); + + if (deserializeResult == DeserializeResult::FatalError) { return false; } - //* Unconditionnally get the data from the buffer so that the correct amount of data is - //* consumed from the buffer, even when we ignore the command and early out. - const char* requestData = nullptr; - if (cmd->status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { - requestData = GetData(commands, size, cmd->dataLength); - if (requestData == nullptr) { - return false; - } - } - - auto* buffer = mDevice->buffer.GetObject(cmd->bufferId); - uint32_t bufferSerial = mDevice->buffer.GetSerial(cmd->bufferId); + auto* buffer = mDevice->buffer.GetObject(cmd.buffer.id); + uint32_t bufferSerial = mDevice->buffer.GetSerial(cmd.buffer.id); //* 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; } //* 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()) { return true; } @@ -686,14 +635,14 @@ namespace dawn_wire { buffer->requests.erase(requestIt); //* 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 application to crash if we did call the callback. - if (request.size != cmd->dataLength) { + if (request.size != cmd.dataLength) { return false; } - ASSERT(requestData != nullptr); + ASSERT(cmd.data != nullptr); if (buffer->mappedData != nullptr) { return false; @@ -702,32 +651,34 @@ namespace dawn_wire { buffer->isWriteMapped = false; buffer->mappedDataSize = request.size; buffer->mappedData = malloc(request.size); - memcpy(buffer->mappedData, requestData, request.size); + memcpy(buffer->mappedData, cmd.data, request.size); - request.readCallback(static_cast(cmd->status), buffer->mappedData, request.userdata); + request.readCallback(static_cast(cmd.status), buffer->mappedData, request.userdata); } else { - request.readCallback(static_cast(cmd->status), nullptr, request.userdata); + request.readCallback(static_cast(cmd.status), nullptr, request.userdata); } return true; } bool HandleBufferMapWriteAsyncCallback(const char** commands, size_t* size) { - const auto* cmd = GetCommand(commands, size); - if (cmd == nullptr) { + ReturnBufferMapWriteAsyncCallbackCmd cmd; + DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator); + + if (deserializeResult == DeserializeResult::FatalError) { return false; } - auto* buffer = mDevice->buffer.GetObject(cmd->bufferId); - uint32_t bufferSerial = mDevice->buffer.GetSerial(cmd->bufferId); + auto* buffer = mDevice->buffer.GetObject(cmd.buffer.id); + uint32_t bufferSerial = mDevice->buffer.GetSerial(cmd.buffer.id); //* 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; } //* 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()) { return true; } @@ -742,7 +693,7 @@ namespace dawn_wire { buffer->requests.erase(requestIt); //* 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) { return false; } @@ -752,29 +703,31 @@ namespace dawn_wire { buffer->mappedData = malloc(request.size); memset(buffer->mappedData, 0, request.size); - request.writeCallback(static_cast(cmd->status), buffer->mappedData, request.userdata); + request.writeCallback(static_cast(cmd.status), buffer->mappedData, request.userdata); } else { - request.writeCallback(static_cast(cmd->status), nullptr, request.userdata); + request.writeCallback(static_cast(cmd.status), nullptr, request.userdata); } return true; } bool HandleFenceUpdateCompletedValue(const char** commands, size_t* size) { - const auto* cmd = GetCommand(commands, size); - if (cmd == nullptr) { + ReturnFenceUpdateCompletedValueCmd cmd; + DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator); + + if (deserializeResult == DeserializeResult::FatalError) { return false; } - auto* fence = mDevice->fence.GetObject(cmd->fenceId); - uint32_t fenceSerial = mDevice->fence.GetSerial(cmd->fenceId); + auto* fence = mDevice->fence.GetObject(cmd.fence.id); + uint32_t fenceSerial = mDevice->fence.GetSerial(cmd.fence.id); //* 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; } - fence->completedValue = cmd->value; + fence->completedValue = cmd.value; fence->CheckPassedFences(); return true; } diff --git a/generator/templates/dawn_wire/WireCmd.cpp b/generator/templates/dawn_wire/WireCmd.cpp index 8bb4235700..782cbd3d91 100644 --- a/generator/templates/dawn_wire/WireCmd.cpp +++ b/generator/templates/dawn_wire/WireCmd.cpp @@ -12,7 +12,7 @@ //* See the License for the specific language governing permissions and //* limitations under the License. -#include "dawn_wire/WireCmd.h" +#include "dawn_wire/WireCmd_autogen.h" #include "common/Assert.h" @@ -49,10 +49,14 @@ //* Outputs the serialization code to put `in` in `out` {% macro serialize_member(member, in, out) %} {%- 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}}); - {% elif member.type.category == "structure"%} - {{as_cType(member.type.name)}}Serialize({{in}}, &{{out}}, buffer, provider); + {%- elif member.type.category == "structure" -%} + {{as_cType(member.type.name)}}Serialize({{in}}, &{{out}}, buffer + {%- if member.type.has_dawn_object -%} + , provider + {%- endif -%} + ); {%- else -%} {{out}} = {{in}}; {%- endif -%} @@ -61,42 +65,34 @@ //* Outputs the deserialization code to put `in` in `out` {% macro deserialize_member(member, in, out) %} {%- 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}})); - {% elif member.type.category == "structure"%} - DESERIALIZE_TRY({{as_cType(member.type.name)}}Deserialize(&{{out}}, &{{in}}, buffer, size, allocator, resolver)); + {%- elif member.type.category == "structure" -%} + DESERIALIZE_TRY({{as_cType(member.type.name)}}Deserialize(&{{out}}, &{{in}}, buffer, size, allocator + {%- if member.type.has_dawn_object -%} + , resolver + {%- endif -%} + )); {%- else -%} {{out}} = {{in}}; {%- endif -%} {% endmacro %} //* The main [de]serialization macro - //* 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 //* 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 is_method = as_method != None %} - {% set is_struct = as_struct != None %} + {% set Cmd = "Cmd" if is_cmd else "" %} //* 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 //* memory directly following the structure in the buffer. - struct {{name}}Transfer { - {% if is_method %} + struct {{Return}}{{name}}Transfer { + {% if is_cmd %} //* Start the transfer structure with the command ID, so that casting to WireCmd gives the ID. {{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 %} //* 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. - 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); size_t result = 0; @@ -140,24 +136,21 @@ } // GetExtraRequiredSize isn't used for structures that are value members of other structures // 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 //* and `provider` to serialize objects. - void {{name}}Serialize(const {{name}}& record, {{name}}Transfer* transfer, - char** buffer, const ObjectIdProvider& provider) { - DAWN_UNUSED(provider); + void {{Return}}{{name}}Serialize(const {{Return}}{{name}}{{Cmd}}& record, {{Return}}{{name}}Transfer* transfer, + char** buffer + {%- if record.has_dawn_object -%} + , const ObjectIdProvider& provider + {%- endif -%} + ) { DAWN_UNUSED(buffer); //* Handle special transfer members of methods. - {% if is_method %} - {% if as_method.return_type.category == "object" %} - transfer->resultId = record.resultId; - transfer->resultSerial = record.resultSerial; - {% endif %} - + {% if is_cmd %} transfer->commandId = {{Return}}WireCmd::{{name}}; - transfer->self = provider.GetId(record.self); {% endif %} //* 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` //* if needed, using `allocator` to store pointed-to values and `resolver` to translate object //* Ids to actual objects. - DeserializeResult {{name}}Deserialize({{name}}* record, const {{name}}Transfer* transfer, - const char** buffer, size_t* size, DeserializeAllocator* allocator, const ObjectIdResolver& resolver) { + DeserializeResult {{Return}}{{name}}Deserialize({{Return}}{{name}}{{Cmd}}* record, const {{Return}}{{name}}Transfer* transfer, + const char** buffer, size_t* size, DeserializeAllocator* allocator + {%- if record.has_dawn_object -%} + , const ObjectIdResolver& resolver + {%- endif -%} + ) { DAWN_UNUSED(allocator); - DAWN_UNUSED(resolver); DAWN_UNUSED(buffer); 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 {% if is_method %} - {% if as_method.return_type.category == "object" %} - record->resultId = transfer->resultId; - record->resultSerial = transfer->resultSerial; - {% endif %} - - ASSERT(transfer->commandId == {{Return}}WireCmd::{{name}}); - + //* First assign selfId: + //* 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". + //* We can also remove is_method record->selfId = transfer->self; //* 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 //* Maybe monad mechanism. 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 - //* 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 //* can error out in that case. if (record->self == nullptr) { @@ -224,7 +243,7 @@ } {% endif %} - {% if is_struct and as_struct.extensible %} + {% if record.extensible %} record->nextInChain = nullptr; {% endif %} @@ -272,6 +291,47 @@ } {% 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 { // Macro to simplify error handling, similar to DAWN_TRY but for DeserializeResult. @@ -324,46 +384,35 @@ namespace dawn_wire { 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"] %} {% 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 %} - // * Output [de]serialization helpers for methods - {% for type in by_category["object"] %} - {% for method in type.methods %} - {% set name = as_MethodSuffix(type.name, method.name) %} + //* Output [de]serialization helpers for commands + {% for command in cmd_records["command"] %} + {% set name = command.name.CamelCase() %} + {{write_record_serialization_helpers(command, name, command.members, + is_cmd=True, is_method=command.derived_method != None)}} + {% endfor %} - using {{name}} = {{name}}Cmd; - {{write_serialization_methods(name, method.arguments, as_method=method)}} - {% endfor %} + //* Output [de]serialization helpers for return commands + {% for command in cmd_records["return command"] %} + {% 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 %} } // anonymous namespace - {% for type in by_category["object"] %} - {% for method in type.methods %} - {% set name = as_MethodSuffix(type.name, method.name) %} - {% set Cmd = name + "Cmd" %} + {% for command in cmd_records["command"] %} + {{ write_command_serialization_methods(command, False) }} + {% endfor %} - size_t {{Cmd}}::GetRequiredSize() const { - return sizeof({{name}}Transfer) + {{name}}GetExtraRequiredSize(*this); - } - - 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 %} + {% for command in cmd_records["return command"] %} + {{ write_command_serialization_methods(command, True) }} {% endfor %} } // namespace dawn_wire diff --git a/generator/templates/dawn_wire/WireCmd.h b/generator/templates/dawn_wire/WireCmd.h index 28bc1ae5d8..d92de2ce68 100644 --- a/generator/templates/dawn_wire/WireCmd.h +++ b/generator/templates/dawn_wire/WireCmd.h @@ -15,10 +15,16 @@ #ifndef DAWNWIRE_WIRECMD_AUTOGEN_H_ #define DAWNWIRE_WIRECMD_AUTOGEN_H_ +#include + namespace dawn_wire { using ObjectId = uint32_t; using ObjectSerial = uint32_t; + struct ObjectHandle { + ObjectId id; + ObjectSerial serial; + }; enum class DeserializeResult { Success, @@ -61,84 +67,68 @@ namespace dawn_wire { //* Enum used as a prefix to each command on the wire format. enum class WireCmd : uint32_t { - {% for type in by_category["object"] %} - {% for method in type.methods %} - {{as_MethodSuffix(type.name, method.name)}}, - {% endfor %} + {% for command in cmd_records["command"] %} + {{command.name.CamelCase()}}, {% 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 class ReturnWireCmd : uint32_t { - DeviceErrorCallback, - {% for type in by_category["object"] if type.is_builder %} - {{type.name.CamelCase()}}ErrorCallback, + {% for command in cmd_records["return command"] %} + {{command.name.CamelCase()}}, {% endfor %} - BufferMapReadAsyncCallback, - BufferMapWriteAsyncCallback, - FenceUpdateCompletedValue, }; - //* Command for the server calling a builder status callback. - {% for type in by_category["object"] if type.is_builder %} - struct Return{{type.name.CamelCase()}}ErrorCallbackCmd { - ReturnWireCmd commandId = ReturnWireCmd::{{type.name.CamelCase()}}ErrorCallback; +{% macro write_command_struct(command, is_return_command) %} + {% set Return = "Return" if is_return_command else "" %} + {% set Cmd = command.name.CamelCase() + "Cmd" %} + struct {{Return}}{{Cmd}} { + //* From a filled structure, compute how much size will be used in the serialization buffer. + size_t GetRequiredSize() const; - ObjectId builtObjectId; - ObjectSerial builtObjectSerial; - uint32_t status; - size_t messageStrlen; - }; + //* 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 + {%- 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 %} } // namespace dawn_wire diff --git a/generator/templates/dawn_wire/WireServer.cpp b/generator/templates/dawn_wire/WireServer.cpp index e186a4252c..7997f4f044 100644 --- a/generator/templates/dawn_wire/WireServer.cpp +++ b/generator/templates/dawn_wire/WireServer.cpp @@ -14,7 +14,8 @@ #include "dawn_wire/TypeTraits_autogen.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" @@ -32,8 +33,7 @@ namespace dawn_wire { struct MapUserdata { Server* server; - uint32_t bufferId; - uint32_t bufferSerial; + ObjectHandle buffer; uint32_t requestSerial; uint32_t size; bool isWrite; @@ -41,8 +41,7 @@ namespace dawn_wire { struct FenceCompletionUserdata { Server* server; - uint32_t fenceId; - uint32_t fenceSerial; + ObjectHandle fence; uint64_t value; }; @@ -69,8 +68,7 @@ namespace dawn_wire { template struct ObjectData : public ObjectDataBase { - uint32_t builtObjectId = 0; - uint32_t builtObjectSerial = 0; + ObjectHandle builtObject = ObjectHandle{0, 0}; }; template <> @@ -214,59 +212,6 @@ namespace dawn_wire { void ForwardFenceCompletedValue(dawnFenceCompletionStatus status, 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(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 mAllocations; - }; - class Server : public CommandHandler, public ObjectIdResolver { public: Server(dawnDevice device, const dawnProcTable& procs, CommandSerializer* serializer) @@ -294,13 +239,11 @@ namespace dawn_wire { void OnDeviceError(const char* message) { ReturnDeviceErrorCallbackCmd cmd; - cmd.messageStrlen = std::strlen(message); + cmd.message = message; - auto allocCmd = static_cast(GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; - - char* messageAlloc = static_cast(GetCmdSpace(cmd.messageStrlen + 1)); - strcpy(messageAlloc, message); + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); } {% for type in by_category["object"] if type.is_builder%} @@ -319,18 +262,16 @@ namespace dawn_wire { if (status != DAWN_BUILDER_ERROR_STATUS_UNKNOWN) { //* Unknown is the only status that can be returned without a call to GetResult //* so we are guaranteed to have created an object. - ASSERT(builder->builtObjectId != 0); + ASSERT(builder->builtObject.id != 0); Return{{Type}}ErrorCallbackCmd cmd; - cmd.builtObjectId = builder->builtObjectId; - cmd.builtObjectSerial = builder->builtObjectSerial; + cmd.builtObject = builder->builtObject; cmd.status = status; - cmd.messageStrlen = std::strlen(message); + cmd.message = message; - auto allocCmd = static_cast(GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; - char* messageAlloc = static_cast(GetCmdSpace(strlen(message) + 1)); - strcpy(messageAlloc, message); + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); } } {% endfor %} @@ -339,46 +280,44 @@ namespace dawn_wire { std::unique_ptr data(userdata); // Skip sending the callback if the buffer has already been destroyed. - auto* bufferData = mKnownBuffer.Get(data->bufferId); - if (bufferData == nullptr || bufferData->serial != data->bufferSerial) { + auto* bufferData = mKnownBuffer.Get(data->buffer.id); + if (bufferData == nullptr || bufferData->serial != data->buffer.serial) { return; } ReturnBufferMapReadAsyncCallbackCmd cmd; - cmd.bufferId = data->bufferId; - cmd.bufferSerial = data->bufferSerial; + cmd.buffer = data->buffer; cmd.requestSerial = data->requestSerial; cmd.status = status; cmd.dataLength = 0; - - auto allocCmd = static_cast(GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; + cmd.data = reinterpret_cast(ptr); if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { - allocCmd->dataLength = data->size; - - void* dataAlloc = GetCmdSpace(data->size); - memcpy(dataAlloc, ptr, data->size); + cmd.dataLength = data->size; } + + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); } void OnMapWriteAsyncCallback(dawnBufferMapAsyncStatus status, void* ptr, MapUserdata* userdata) { std::unique_ptr data(userdata); // Skip sending the callback if the buffer has already been destroyed. - auto* bufferData = mKnownBuffer.Get(data->bufferId); - if (bufferData == nullptr || bufferData->serial != data->bufferSerial) { + auto* bufferData = mKnownBuffer.Get(data->buffer.id); + if (bufferData == nullptr || bufferData->serial != data->buffer.serial) { return; } ReturnBufferMapWriteAsyncCallbackCmd cmd; - cmd.bufferId = data->bufferId; - cmd.bufferSerial = data->bufferSerial; + cmd.buffer = data->buffer; cmd.requestSerial = data->requestSerial; cmd.status = status; - auto allocCmd = static_cast(GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { bufferData->mappedData = ptr; @@ -390,15 +329,14 @@ namespace dawn_wire { std::unique_ptr data(userdata); ReturnFenceUpdateCompletedValueCmd cmd; - cmd.fenceId = data->fenceId; - cmd.fenceSerial = data->fenceSerial; + cmd.fence = data->fence; cmd.value = data->value; - auto allocCmd = static_cast(GetCmdSpace(sizeof(cmd))); - *allocCmd = cmd; + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); } - {% set client_side_commands = ["FenceGetCompletedValue"] %} const char* HandleCommands(const char* commands, size_t size) override { mProcs.deviceTick(mKnownDevice.Get(1)->handle); @@ -407,25 +345,11 @@ namespace dawn_wire { bool success = false; switch (cmdId) { - {% for type in by_category["object"] %} - {% for method in type.methods %} - {% set Suffix = as_MethodSuffix(type.name, method.name) %} - {% if Suffix not in client_side_commands %} - case WireCmd::{{Suffix}}: - success = Handle{{Suffix}}(&commands, &size); - break; - {% endif %} - {% endfor %} + {% for command in cmd_records["command"] %} + case WireCmd::{{command.name.CamelCase()}}: + success = Handle{{command.name.CamelCase()}}(&commands, &size); + break; {% 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: success = false; } @@ -447,7 +371,7 @@ namespace dawn_wire { dawnProcTable mProcs; CommandSerializer* mSerializer = nullptr; - ServerAllocator mAllocator; + WireDeserializeAllocator mAllocator; void* GetCmdSpace(size_t size) { return mSerializer->GetCmdSpace(size); @@ -484,36 +408,10 @@ namespace dawn_wire { KnownObjects<{{as_cType(type.name)}}> mKnown{{type.name.CamelCase()}}; {% endfor %} - {% set reverse_lookup_object_types = ["Fence"] %} - {% for type in by_category["object"] if type.name.CamelCase() in reverse_lookup_object_types %} + {% for type in by_category["object"] if type.name.CamelCase() in server_reverse_lookup_objects %} ObjectIdLookupTable<{{as_cType(type.name)}}> m{{type.name.CamelCase()}}IdTable; {% 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 - 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(*buffer); - - *buffer += totalSize; - *size -= totalSize; - - return data; - } - template - static const T* GetCommand(const char** commands, size_t* size) { - return GetData(commands, size, 1); - } - - {% set custom_pre_handler_commands = ["BufferUnmap"] %} - bool PreHandleBufferUnmap(const BufferUnmapCmd& cmd) { auto* selfData = mKnownBuffer.Get(cmd.selfId); ASSERT(selfData != nullptr); @@ -523,8 +421,6 @@ namespace dawn_wire { return true; } - {% set custom_post_handler_commands = ["QueueSignal"] %} - bool PostHandleQueueSignal(const QueueSignalCmd& cmd) { if (cmd.fence == nullptr) { return false; @@ -536,8 +432,7 @@ namespace dawn_wire { auto* data = new FenceCompletionUserdata; data->server = this; - data->fenceId = fenceId; - data->fenceSerial = fence->serial; + data->fence = ObjectHandle{fenceId, fence->serial}; data->value = cmd.signalValue; auto userdata = static_cast(reinterpret_cast(data)); @@ -560,7 +455,7 @@ namespace dawn_wire { return false; } - {% if Suffix in custom_pre_handler_commands %} + {% if Suffix in server_custom_pre_handler_commands %} if (!PreHandle{{Suffix}}(cmd)) { return false; } @@ -575,15 +470,14 @@ namespace dawn_wire { {% set returns = return_type.name.canonical_case() != "void" %} {% if returns %} {% 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) { return false; } - resultData->serial = cmd.resultSerial; + resultData->serial = cmd.result.serial; {% if type.is_builder %} - selfData->builtObjectId = cmd.resultId; - selfData->builtObjectSerial = cmd.resultSerial; + selfData->builtObject = cmd.result; {% endif %} {% endif %} @@ -608,7 +502,7 @@ namespace dawn_wire { {%- endfor -%} ); - {% if Suffix in custom_post_handler_commands %} + {% if Suffix in server_custom_post_handler_commands %} if (!PostHandle{{Suffix}}(cmd)) { return false; } @@ -618,10 +512,10 @@ namespace dawn_wire { resultData->handle = result; 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 if (result) { - m{{return_type.name.CamelCase()}}IdTable.Store(result, cmd.resultId); + m{{return_type.name.CamelCase()}}IdTable.Store(result, cmd.result.id); } {% endif %} @@ -630,7 +524,7 @@ namespace dawn_wire { {% if return_type.is_builder %} if (result != nullptr) { uint64_t userdata1 = static_cast(reinterpret_cast(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); } {% endif %} @@ -645,16 +539,18 @@ namespace dawn_wire { bool HandleBufferMapAsync(const char** commands, size_t* size) { //* These requests are just forwarded to the buffer, with userdata containing what the client //* will require in the return command. - const auto* cmd = GetCommand(commands, size); - if (cmd == nullptr) { + BufferMapAsyncCmd cmd; + DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator); + + if (deserializeResult == DeserializeResult::FatalError) { return false; } - ObjectId bufferId = cmd->bufferId; - uint32_t requestSerial = cmd->requestSerial; - uint32_t requestSize = cmd->size; - uint32_t requestStart = cmd->start; - bool isWrite = cmd->isWrite; + ObjectId bufferId = cmd.bufferId; + uint32_t requestSerial = cmd.requestSerial; + uint32_t requestSize = cmd.size; + uint32_t requestStart = cmd.start; + bool isWrite = cmd.isWrite; //* The null object isn't valid as `self` if (bufferId == 0) { @@ -668,8 +564,7 @@ namespace dawn_wire { auto* data = new MapUserdata; data->server = this; - data->bufferId = bufferId; - data->bufferSerial = buffer->serial; + data->buffer = ObjectHandle{bufferId, buffer->serial}; data->requestSerial = requestSerial; data->size = requestSize; data->isWrite = isWrite; @@ -696,13 +591,15 @@ namespace dawn_wire { } bool HandleBufferUpdateMappedData(const char** commands, size_t* size) { - const auto* cmd = GetCommand(commands, size); - if (cmd == nullptr) { + BufferUpdateMappedDataCmd cmd; + DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator); + + if (deserializeResult == DeserializeResult::FatalError) { return false; } - ObjectId bufferId = cmd->bufferId; - size_t dataLength = cmd->dataLength; + ObjectId bufferId = cmd.bufferId; + size_t dataLength = cmd.dataLength; //* The null object isn't valid as `self` if (bufferId == 0) { @@ -715,29 +612,28 @@ namespace dawn_wire { return false; } - const char* data = GetData(commands, size, dataLength); - if (data == nullptr) { - return false; - } + DAWN_ASSERT(cmd.data != nullptr); - memcpy(buffer->mappedData, data, dataLength); + memcpy(buffer->mappedData, cmd.data, dataLength); return true; } bool HandleDestroyObject(const char** commands, size_t* size) { - const auto* cmd = GetCommand(commands, size); - if (cmd == nullptr) { + DestroyObjectCmd cmd; + DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator); + + if (deserializeResult == DeserializeResult::FatalError) { return false; } - ObjectId objectId = cmd->objectId; + ObjectId objectId = cmd.objectId; //* ID 0 are reserved for nullptr and cannot be destroyed. if (objectId == 0) { return false; } - switch (cmd->objectType) { + switch (cmd.objectType) { {% for type in by_category["object"] %} {% set ObjectType = type.name.CamelCase() %} case ObjectType::{{ObjectType}}: { @@ -749,7 +645,7 @@ namespace dawn_wire { if (data == nullptr) { 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); {% endif %} diff --git a/generator/wire_cmd.py b/generator/wire_cmd.py new file mode 100644 index 0000000000..73669ca7d8 --- /dev/null +++ b/generator/wire_cmd.py @@ -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 diff --git a/src/dawn_wire/WireCmd.h b/src/dawn_wire/WireCmd.h deleted file mode 100644 index 3dfe6b9ff1..0000000000 --- a/src/dawn_wire/WireCmd.h +++ /dev/null @@ -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 - -#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_ diff --git a/src/dawn_wire/WireDeserializeAllocator.cpp b/src/dawn_wire/WireDeserializeAllocator.cpp new file mode 100644 index 0000000000..7ae1b35c3f --- /dev/null +++ b/src/dawn_wire/WireDeserializeAllocator.cpp @@ -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 + +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(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 diff --git a/src/dawn_wire/WireDeserializeAllocator.h b/src/dawn_wire/WireDeserializeAllocator.h new file mode 100644 index 0000000000..c900a23cbd --- /dev/null +++ b/src/dawn_wire/WireDeserializeAllocator.h @@ -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 + +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 mAllocations; + }; +} // namespace dawn_wire + +#endif // DAWNWIRE_WIREDESERIALIZEALLOCATOR_H_