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>
diff --git a/.gitignore b/.gitignore
index 5ab63d2..1dc05c7 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 5d61bc1..91b64f5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -54,6 +54,8 @@
   # 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 @@
   ]
 
   deps = [
-    ":libdawn_native_sources",
     ":dawn_common",
+    ":libdawn_native_sources",
   ]
   sources = [
     "src/dawn_native/DawnNative.cpp",
@@ -761,7 +763,10 @@
 
   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 @@
     ":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 44b9814..6848bd8 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 0000000..eaa93b2
--- /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 0000000..977dee9
--- /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 a5c5a7d..32cf516 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 @@
     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 @@
         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 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 @@
 
     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 @@
     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 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 @@
         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 @@
         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 @@
     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 @@
     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 ce708e4..9f8dd49 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 @@
             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 @@
                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 @@
                                 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 @@
                     cmd.objectType = ObjectType::{{type.name.CamelCase()}};
                     cmd.objectId = obj->id;
 
-                    auto allocCmd = static_cast<decltype(cmd)*>(obj->device->GetCmdSpace(sizeof(cmd)));
-                    *allocCmd = cmd;
+                    size_t requiredSize = cmd.GetRequiredSize();
+                    char* allocatedBuffer = static_cast<char*>(obj->device->GetCmdSpace(requiredSize));
+                    cmd.Serialize(allocatedBuffer);
 
                     obj->device->{{type.name.camelCase()}}.Free(obj);
                 }
@@ -386,8 +380,9 @@
             cmd.size = size;
             cmd.isWrite = false;
 
-            auto allocCmd = static_cast<decltype(cmd)*>(buffer->device->GetCmdSpace(sizeof(cmd)));
-            *allocCmd = cmd;
+            size_t requiredSize = cmd.GetRequiredSize();
+            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) {
@@ -410,8 +405,9 @@
             cmd.size = size;
             cmd.isWrite = true;
 
-            auto allocCmd = static_cast<decltype(cmd)*>(buffer->device->GetCmdSpace(sizeof(cmd)));
-            *allocCmd = cmd;
+            size_t requiredSize = cmd.GetRequiredSize();
+            char* allocatedBuffer = static_cast<char*>(buffer->device->GetCmdSpace(requiredSize));
+            cmd.Serialize(allocatedBuffer);
         }
 
         uint64_t ClientFenceGetCompletedValue(dawnFence cSelf) {
@@ -458,12 +454,11 @@
                     BufferUpdateMappedDataCmd cmd;
                     cmd.bufferId = buffer->id;
                     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)));
-                    *allocCmd = cmd;
-
-                    void* dataAlloc = buffer->device->GetCmdSpace(cmd.dataLength);
-                    memcpy(dataAlloc, buffer->mappedData, cmd.dataLength);
+                    size_t requiredSize = cmd.GetRequiredSize();
+                    char* allocatedBuffer = static_cast<char*>(buffer->device->GetCmdSpace(requiredSize));
+                    cmd.Serialize(allocatedBuffer);
                 }
 
                 free(buffer->mappedData);
@@ -510,14 +505,12 @@
         //  - 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 @@
 
                         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 @@
                         if (!success) {
                             return nullptr;
                         }
+                        mAllocator.Reset();
                     }
 
                     if (size != 0) {
@@ -573,72 +555,47 @@
 
             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 <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;
-                    }
+                bool HandleDeviceErrorCallback(const char** commands, size_t* size) {
+                    ReturnDeviceErrorCallbackCmd cmd;
+                    DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator);
 
-                    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);
-                }
-
-                bool HandleDeviceErrorCallbackCmd(const char** commands, size_t* size) {
-                    const auto* cmd = GetCommand<ReturnDeviceErrorCallbackCmd>(commands, size);
-                    if (cmd == nullptr) {
+                    if (deserializeResult == DeserializeResult::FatalError) {
                         return false;
                     }
 
-                    const char* message = GetData<char>(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<Return{{Type}}ErrorCallbackCmd>(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<char>(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<dawnBuilderErrorStatus>(cmd->status), message);
+                        bool called = builtObject->builderCallback.Call(static_cast<dawnBuilderErrorStatus>(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 @@
                 {% endfor %}
 
                 bool HandleBufferMapReadAsyncCallback(const char** commands, size_t* size) {
-                    const auto* cmd = GetCommand<ReturnBufferMapReadAsyncCallbackCmd>(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<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);
+                    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 @@
                     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 @@
                         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<dawnBufferMapAsyncStatus>(cmd->status), buffer->mappedData, request.userdata);
+                        request.readCallback(static_cast<dawnBufferMapAsyncStatus>(cmd.status), buffer->mappedData, request.userdata);
                     } else {
-                        request.readCallback(static_cast<dawnBufferMapAsyncStatus>(cmd->status), nullptr, request.userdata);
+                        request.readCallback(static_cast<dawnBufferMapAsyncStatus>(cmd.status), nullptr, request.userdata);
                     }
 
                     return true;
                 }
 
                 bool HandleBufferMapWriteAsyncCallback(const char** commands, size_t* size) {
-                    const auto* cmd = GetCommand<ReturnBufferMapWriteAsyncCallbackCmd>(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 @@
                     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 @@
                         buffer->mappedData = malloc(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 {
-                        request.writeCallback(static_cast<dawnBufferMapAsyncStatus>(cmd->status), nullptr, request.userdata);
+                        request.writeCallback(static_cast<dawnBufferMapAsyncStatus>(cmd.status), nullptr, request.userdata);
                     }
 
                     return true;
                 }
 
                 bool HandleFenceUpdateCompletedValue(const char** commands, size_t* size) {
-                    const auto* cmd = GetCommand<ReturnFenceUpdateCompletedValueCmd>(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 8bb4235..782cbd3 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 @@
             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 28bc1ae..d92de2c 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 <dawn/dawn.h>
+
 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 @@
 
     //* 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 e186a42..7997f4f 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 @@
 
         struct MapUserdata {
             Server* server;
-            uint32_t bufferId;
-            uint32_t bufferSerial;
+            ObjectHandle buffer;
             uint32_t requestSerial;
             uint32_t size;
             bool isWrite;
@@ -41,8 +41,7 @@
 
         struct FenceCompletionUserdata {
             Server* server;
-            uint32_t fenceId;
-            uint32_t fenceSerial;
+            ObjectHandle fence;
             uint64_t value;
         };
 
@@ -69,8 +68,7 @@
 
         template <typename T>
         struct ObjectData<T, true> : public ObjectDataBase<T> {
-            uint32_t builtObjectId = 0;
-            uint32_t builtObjectSerial = 0;
+            ObjectHandle builtObject = ObjectHandle{0, 0};
         };
 
         template <>
@@ -214,59 +212,6 @@
         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<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 {
             public:
                 Server(dawnDevice device, const dawnProcTable& procs, CommandSerializer* serializer)
@@ -294,13 +239,11 @@
 
                 void OnDeviceError(const char* message) {
                     ReturnDeviceErrorCallbackCmd cmd;
-                    cmd.messageStrlen = std::strlen(message);
+                    cmd.message = message;
 
-                    auto allocCmd = static_cast<ReturnDeviceErrorCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
-                    *allocCmd = cmd;
-
-                    char* messageAlloc = static_cast<char*>(GetCmdSpace(cmd.messageStrlen + 1));
-                    strcpy(messageAlloc, message);
+                    size_t requiredSize = cmd.GetRequiredSize();
+                    char* allocatedBuffer = static_cast<char*>(GetCmdSpace(requiredSize));
+                    cmd.Serialize(allocatedBuffer);
                 }
 
                 {% for type in by_category["object"] if type.is_builder%}
@@ -319,18 +262,16 @@
                         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<Return{{Type}}ErrorCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
-                            *allocCmd = cmd;
-                            char* messageAlloc = static_cast<char*>(GetCmdSpace(strlen(message) + 1));
-                            strcpy(messageAlloc, message);
+                            size_t requiredSize = cmd.GetRequiredSize();
+                            char* allocatedBuffer = static_cast<char*>(GetCmdSpace(requiredSize));
+                            cmd.Serialize(allocatedBuffer);
                         }
                     }
                 {% endfor %}
@@ -339,46 +280,44 @@
                     std::unique_ptr<MapUserdata> 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<ReturnBufferMapReadAsyncCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
-                    *allocCmd = cmd;
+                    cmd.data = reinterpret_cast<const uint8_t*>(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<char*>(GetCmdSpace(requiredSize));
+                    cmd.Serialize(allocatedBuffer);
                 }
 
                 void OnMapWriteAsyncCallback(dawnBufferMapAsyncStatus status, void* ptr, MapUserdata* userdata) {
                     std::unique_ptr<MapUserdata> 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<ReturnBufferMapWriteAsyncCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
-                    *allocCmd = cmd;
+                    size_t requiredSize = cmd.GetRequiredSize();
+                    char* allocatedBuffer = static_cast<char*>(GetCmdSpace(requiredSize));
+                    cmd.Serialize(allocatedBuffer);
 
                     if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
                         bufferData->mappedData = ptr;
@@ -390,15 +329,14 @@
                     std::unique_ptr<FenceCompletionUserdata> data(userdata);
 
                     ReturnFenceUpdateCompletedValueCmd cmd;
-                    cmd.fenceId = data->fenceId;
-                    cmd.fenceSerial = data->fenceSerial;
+                    cmd.fence = data->fence;
                     cmd.value = data->value;
 
-                    auto allocCmd = static_cast<ReturnFenceUpdateCompletedValueCmd*>(GetCmdSpace(sizeof(cmd)));
-                    *allocCmd = cmd;
+                    size_t requiredSize = cmd.GetRequiredSize();
+                    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 {
                     mProcs.deviceTick(mKnownDevice.Get(1)->handle);
 
@@ -407,25 +345,11 @@
 
                         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 @@
                 dawnProcTable mProcs;
                 CommandSerializer* mSerializer = nullptr;
 
-                ServerAllocator mAllocator;
+                WireDeserializeAllocator mAllocator;
 
                 void* GetCmdSpace(size_t size) {
                     return mSerializer->GetCmdSpace(size);
@@ -484,36 +408,10 @@
                     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 <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) {
                     auto* selfData = mKnownBuffer.Get(cmd.selfId);
                     ASSERT(selfData != nullptr);
@@ -523,8 +421,6 @@
                     return true;
                 }
 
-                {% set custom_post_handler_commands = ["QueueSignal"] %}
-
                 bool PostHandleQueueSignal(const QueueSignalCmd& cmd) {
                     if (cmd.fence == nullptr) {
                         return false;
@@ -536,8 +432,7 @@
 
                     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<uint64_t>(reinterpret_cast<uintptr_t>(data));
@@ -560,7 +455,7 @@
                                     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 @@
                                 {% 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 @@
                                     {%- 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 @@
                                     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 @@
                                     {% if return_type.is_builder %}
                                         if (result != nullptr) {
                                             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);
                                         }
                                     {% endif %}
@@ -645,16 +539,18 @@
                 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<BufferMapAsyncCmd>(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 @@
 
                     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 @@
                 }
 
                 bool HandleBufferUpdateMappedData(const char** commands, size_t* size) {
-                    const auto* cmd = GetCommand<BufferUpdateMappedDataCmd>(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 @@
                         return false;
                     }
 
-                    const char* data = GetData<char>(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<DestroyObjectCmd>(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 @@
                                     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 0000000..73669ca
--- /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 3dfe6b9..0000000
--- 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 <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_
diff --git a/src/dawn_wire/WireDeserializeAllocator.cpp b/src/dawn_wire/WireDeserializeAllocator.cpp
new file mode 100644
index 0000000..7ae1b35
--- /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 <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
diff --git a/src/dawn_wire/WireDeserializeAllocator.h b/src/dawn_wire/WireDeserializeAllocator.h
new file mode 100644
index 0000000..c900a23
--- /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 <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_