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/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 %}