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

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

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

149
generator/common.py Normal file
View File

@@ -0,0 +1,149 @@
# Copyright 2017 The Dawn Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import namedtuple
class Name:
def __init__(self, name, native=False):
self.native = native
if native:
self.chunks = [name]
else:
self.chunks = name.split(' ')
def CamelChunk(self, chunk):
return chunk[0].upper() + chunk[1:]
def canonical_case(self):
return (' '.join(self.chunks)).lower()
def concatcase(self):
return ''.join(self.chunks)
def camelCase(self):
return self.chunks[0] + ''.join([self.CamelChunk(chunk) for chunk in self.chunks[1:]])
def CamelCase(self):
return ''.join([self.CamelChunk(chunk) for chunk in self.chunks])
def SNAKE_CASE(self):
return '_'.join([chunk.upper() for chunk in self.chunks])
def snake_case(self):
return '_'.join(self.chunks)
class Type:
def __init__(self, name, json_data, native=False):
self.json_data = json_data
self.dict_name = name
self.name = Name(name, native=native)
self.category = json_data['category']
self.is_builder = self.name.canonical_case().endswith(" builder")
EnumValue = namedtuple('EnumValue', ['name', 'value'])
class EnumType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.values = [EnumValue(Name(m['name']), m['value']) for m in self.json_data['values']]
BitmaskValue = namedtuple('BitmaskValue', ['name', 'value'])
class BitmaskType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.values = [BitmaskValue(Name(m['name']), m['value']) for m in self.json_data['values']]
self.full_mask = 0
for value in self.values:
self.full_mask = self.full_mask | value.value
class NativeType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data, native=True)
class NativelyDefined(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
# Methods and structures are both "records", so record members correspond to
# method arguments or structure members.
class RecordMember:
def __init__(self, name, typ, annotation, optional, is_return_value):
self.name = name
self.type = typ
self.annotation = annotation
self.length = None
self.optional = optional
self.is_return_value = is_return_value
Method = namedtuple('Method', ['name', 'return_type', 'arguments'])
class ObjectType(Type):
def __init__(self, name, json_data):
Type.__init__(self, name, json_data)
self.methods = []
self.native_methods = []
self.built_type = None
class Record:
def __init__(self, name):
self.name = Name(name)
self.members = []
self.has_dawn_object = False
def update_metadata(self):
def has_dawn_object(member):
if isinstance(member.type, ObjectType):
return True
elif isinstance(member.type, StructureType):
return member.type.has_dawn_object
else:
return False
self.has_dawn_object = any(has_dawn_object(member) for member in self.members)
class StructureType(Record, Type):
def __init__(self, name, json_data):
Record.__init__(self, name)
Type.__init__(self, name, json_data)
self.extensible = json_data.get("extensible", False)
class Command(Record):
def __init__(self, name, members=None):
Record.__init__(self, name)
self.members = members or []
self.derived_object = None
self.derived_method = None
def linked_record_members(json_data, types):
members = []
members_by_name = {}
for m in json_data:
member = RecordMember(Name(m['name']), types[m['type']],
m.get('annotation', 'value'), m.get('optional', False),
m.get('is_return_value', False))
members.append(member)
members_by_name[member.name.canonical_case()] = member
for (member, m) in zip(members, json_data):
if member.annotation != 'value':
if not 'length' in m:
if member.type.category == 'structure':
member.length = "constant"
member.constant_length = 1
else:
assert(False)
elif m['length'] == 'strlen':
member.length = 'strlen'
else:
member.length = members_by_name[m['length']]
return members

View File

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

View File

@@ -13,7 +13,8 @@
//* limitations under the License.
#include "dawn_wire/Wire.h"
#include "dawn_wire/WireCmd.h"
#include "dawn_wire/WireCmd_autogen.h"
#include "dawn_wire/WireDeserializeAllocator.h"
#include "common/Assert.h"
#include "common/SerialMap.h"
@@ -66,12 +67,7 @@ namespace dawn_wire {
BuilderCallbackData builderCallback;
};
{% set special_objects = [
"device",
"buffer",
"fence",
] %}
{% for type in by_category["object"] if not type.name.canonical_case() in special_objects %}
{% for type in by_category["object"] if not type.name.CamelCase() in client_special_objects %}
struct {{type.name.CamelCase()}} : ObjectBase {
using ObjectBase::ObjectBase;
};
@@ -269,8 +265,6 @@ namespace dawn_wire {
CommandSerializer* mSerializer = nullptr;
};
{% set client_side_commands = ["FenceGetCompletedValue"] %}
//* Implementation of the client API functions.
{% for type in by_category["object"] %}
{% set Type = type.name.CamelCase() %}
@@ -305,8 +299,7 @@ namespace dawn_wire {
self->builderCallback.canCall = false;
{% endif %}
cmd.resultId = allocation->object->id;
cmd.resultSerial = allocation->serial;
cmd.result = ObjectHandle{allocation->object->id, allocation->serial};
{% endif %}
{% for arg in method.arguments %}
@@ -353,8 +346,9 @@ namespace dawn_wire {
cmd.objectType = ObjectType::{{type.name.CamelCase()}};
cmd.objectId = obj->id;
auto allocCmd = static_cast<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 @@ namespace dawn_wire {
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 @@ namespace dawn_wire {
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 @@ namespace dawn_wire {
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 @@ namespace dawn_wire {
// - An autogenerated Client{{suffix}} method that sends the command on the wire
// - A manual ProxyClient{{suffix}} method that will be inserted in the proctable instead of
// the autogenerated one, and that will have to call Client{{suffix}}
{% set proxied_commands = ["BufferUnmap", "DeviceCreateFence", "QueueSignal"] %}
dawnProcTable GetProcs() {
dawnProcTable table;
{% for type in by_category["object"] %}
{% for method in native_methods(type) %}
{% set suffix = as_MethodSuffix(type.name, method.name) %}
{% if suffix in proxied_commands %}
{% if suffix in client_proxied_commands %}
table.{{as_varName(type.name, method.name)}} = ProxyClient{{suffix}};
{% else %}
table.{{as_varName(type.name, method.name)}} = Client{{suffix}};
@@ -538,23 +531,11 @@ namespace dawn_wire {
bool success = false;
switch (cmdId) {
case ReturnWireCmd::DeviceErrorCallback:
success = HandleDeviceErrorCallbackCmd(&commands, &size);
break;
{% for type in by_category["object"] if type.is_builder %}
case ReturnWireCmd::{{type.name.CamelCase()}}ErrorCallback:
success = Handle{{type.name.CamelCase()}}ErrorCallbackCmd(&commands, &size);
{% for command in cmd_records["return command"] %}
case ReturnWireCmd::{{command.name.CamelCase()}}:
success = Handle{{command.name.CamelCase()}}(&commands, &size);
break;
{% endfor %}
case ReturnWireCmd::BufferMapReadAsyncCallback:
success = HandleBufferMapReadAsyncCallback(&commands, &size);
break;
case ReturnWireCmd::BufferMapWriteAsyncCallback:
success = HandleBufferMapWriteAsyncCallback(&commands, &size);
break;
case ReturnWireCmd::FenceUpdateCompletedValue:
success = HandleFenceUpdateCompletedValue(&commands, &size);
break;
default:
success = false;
}
@@ -562,6 +543,7 @@ namespace dawn_wire {
if (!success) {
return nullptr;
}
mAllocator.Reset();
}
if (size != 0) {
@@ -573,72 +555,47 @@ namespace dawn_wire {
private:
Device* mDevice = nullptr;
WireDeserializeAllocator mAllocator;
//* Helper function for the getting of the command data in command handlers.
//* Checks there is enough data left, updates the buffer / size and returns
//* the command (or nullptr for an error).
template <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 @@ namespace dawn_wire {
{% 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 @@ namespace dawn_wire {
buffer->requests.erase(requestIt);
//* On success, we copy the data locally because the IPC buffer isn't valid outside of this function
if (cmd->status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
if (cmd.status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
//* The server didn't send the right amount of data, this is an error and could cause
//* the application to crash if we did call the callback.
if (request.size != cmd->dataLength) {
if (request.size != cmd.dataLength) {
return false;
}
ASSERT(requestData != nullptr);
ASSERT(cmd.data != nullptr);
if (buffer->mappedData != nullptr) {
return false;
@@ -702,32 +651,34 @@ namespace dawn_wire {
buffer->isWriteMapped = false;
buffer->mappedDataSize = request.size;
buffer->mappedData = malloc(request.size);
memcpy(buffer->mappedData, requestData, request.size);
memcpy(buffer->mappedData, cmd.data, request.size);
request.readCallback(static_cast<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 @@ namespace dawn_wire {
buffer->requests.erase(requestIt);
//* On success, we copy the data locally because the IPC buffer isn't valid outside of this function
if (cmd->status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
if (cmd.status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
if (buffer->mappedData != nullptr) {
return false;
}
@@ -752,29 +703,31 @@ namespace dawn_wire {
buffer->mappedData = malloc(request.size);
memset(buffer->mappedData, 0, request.size);
request.writeCallback(static_cast<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;
}

View File

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

View File

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

View File

@@ -14,7 +14,8 @@
#include "dawn_wire/TypeTraits_autogen.h"
#include "dawn_wire/Wire.h"
#include "dawn_wire/WireCmd.h"
#include "dawn_wire/WireCmd_autogen.h"
#include "dawn_wire/WireDeserializeAllocator.h"
#include "common/Assert.h"
@@ -32,8 +33,7 @@ namespace dawn_wire {
struct MapUserdata {
Server* server;
uint32_t bufferId;
uint32_t bufferSerial;
ObjectHandle buffer;
uint32_t requestSerial;
uint32_t size;
bool isWrite;
@@ -41,8 +41,7 @@ namespace dawn_wire {
struct FenceCompletionUserdata {
Server* server;
uint32_t fenceId;
uint32_t fenceSerial;
ObjectHandle fence;
uint64_t value;
};
@@ -69,8 +68,7 @@ namespace dawn_wire {
template <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 @@ namespace dawn_wire {
void ForwardFenceCompletedValue(dawnFenceCompletionStatus status,
dawnCallbackUserdata userdata);
// A really really simple implementation of the DeserializeAllocator. It's main feature
// is that it has some inline storage so as to avoid allocations for the majority of
// commands.
class ServerAllocator : public DeserializeAllocator {
public:
ServerAllocator() {
Reset();
}
~ServerAllocator() {
Reset();
}
void* GetSpace(size_t size) override {
// Return space in the current buffer if possible first.
if (mRemainingSize >= size) {
char* buffer = mCurrentBuffer;
mCurrentBuffer += size;
mRemainingSize -= size;
return buffer;
}
// Otherwise allocate a new buffer and try again.
size_t allocationSize = std::max(size, size_t(2048));
char* allocation = static_cast<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 @@ namespace dawn_wire {
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 @@ namespace dawn_wire {
if (status != DAWN_BUILDER_ERROR_STATUS_UNKNOWN) {
//* Unknown is the only status that can be returned without a call to GetResult
//* so we are guaranteed to have created an object.
ASSERT(builder->builtObjectId != 0);
ASSERT(builder->builtObject.id != 0);
Return{{Type}}ErrorCallbackCmd cmd;
cmd.builtObjectId = builder->builtObjectId;
cmd.builtObjectSerial = builder->builtObjectSerial;
cmd.builtObject = builder->builtObject;
cmd.status = status;
cmd.messageStrlen = std::strlen(message);
cmd.message = message;
auto allocCmd = static_cast<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 @@ namespace dawn_wire {
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 @@ namespace dawn_wire {
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 @@ namespace dawn_wire {
bool success = false;
switch (cmdId) {
{% for type in by_category["object"] %}
{% for method in type.methods %}
{% set Suffix = as_MethodSuffix(type.name, method.name) %}
{% if Suffix not in client_side_commands %}
case WireCmd::{{Suffix}}:
success = Handle{{Suffix}}(&commands, &size);
break;
{% endif %}
{% endfor %}
{% for command in cmd_records["command"] %}
case WireCmd::{{command.name.CamelCase()}}:
success = Handle{{command.name.CamelCase()}}(&commands, &size);
break;
{% endfor %}
case WireCmd::BufferMapAsync:
success = HandleBufferMapAsync(&commands, &size);
break;
case WireCmd::BufferUpdateMappedDataCmd:
success = HandleBufferUpdateMappedData(&commands, &size);
break;
case WireCmd::DestroyObject:
success = HandleDestroyObject(&commands, &size);
break;
default:
success = false;
}
@@ -447,7 +371,7 @@ namespace dawn_wire {
dawnProcTable mProcs;
CommandSerializer* mSerializer = nullptr;
ServerAllocator mAllocator;
WireDeserializeAllocator mAllocator;
void* GetCmdSpace(size_t size) {
return mSerializer->GetCmdSpace(size);
@@ -484,36 +408,10 @@ namespace dawn_wire {
KnownObjects<{{as_cType(type.name)}}> mKnown{{type.name.CamelCase()}};
{% endfor %}
{% set reverse_lookup_object_types = ["Fence"] %}
{% for type in by_category["object"] if type.name.CamelCase() in reverse_lookup_object_types %}
{% for type in by_category["object"] if type.name.CamelCase() in server_reverse_lookup_objects %}
ObjectIdLookupTable<{{as_cType(type.name)}}> m{{type.name.CamelCase()}}IdTable;
{% endfor %}
//* Helper function for the getting of the command data in command handlers.
//* Checks there is enough data left, updates the buffer / size and returns
//* the command (or nullptr for an error).
template <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 @@ namespace dawn_wire {
return true;
}
{% set custom_post_handler_commands = ["QueueSignal"] %}
bool PostHandleQueueSignal(const QueueSignalCmd& cmd) {
if (cmd.fence == nullptr) {
return false;
@@ -536,8 +432,7 @@ namespace dawn_wire {
auto* data = new FenceCompletionUserdata;
data->server = this;
data->fenceId = fenceId;
data->fenceSerial = fence->serial;
data->fence = ObjectHandle{fenceId, fence->serial};
data->value = cmd.signalValue;
auto userdata = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data));
@@ -560,7 +455,7 @@ namespace dawn_wire {
return false;
}
{% if Suffix in custom_pre_handler_commands %}
{% if Suffix in server_custom_pre_handler_commands %}
if (!PreHandle{{Suffix}}(cmd)) {
return false;
}
@@ -575,15 +470,14 @@ namespace dawn_wire {
{% set returns = return_type.name.canonical_case() != "void" %}
{% if returns %}
{% set Type = method.return_type.name.CamelCase() %}
auto* resultData = mKnown{{Type}}.Allocate(cmd.resultId);
auto* resultData = mKnown{{Type}}.Allocate(cmd.result.id);
if (resultData == nullptr) {
return false;
}
resultData->serial = cmd.resultSerial;
resultData->serial = cmd.result.serial;
{% if type.is_builder %}
selfData->builtObjectId = cmd.resultId;
selfData->builtObjectSerial = cmd.resultSerial;
selfData->builtObject = cmd.result;
{% endif %}
{% endif %}
@@ -608,7 +502,7 @@ namespace dawn_wire {
{%- endfor -%}
);
{% if Suffix in custom_post_handler_commands %}
{% if Suffix in server_custom_post_handler_commands %}
if (!PostHandle{{Suffix}}(cmd)) {
return false;
}
@@ -618,10 +512,10 @@ namespace dawn_wire {
resultData->handle = result;
resultData->valid = result != nullptr;
{% if return_type.name.CamelCase() in reverse_lookup_object_types %}
{% if return_type.name.CamelCase() in server_reverse_lookup_objects %}
//* For created objects, store a mapping from them back to their client IDs
if (result) {
m{{return_type.name.CamelCase()}}IdTable.Store(result, cmd.resultId);
m{{return_type.name.CamelCase()}}IdTable.Store(result, cmd.result.id);
}
{% endif %}
@@ -630,7 +524,7 @@ namespace dawn_wire {
{% if return_type.is_builder %}
if (result != nullptr) {
uint64_t userdata1 = static_cast<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 @@ namespace dawn_wire {
bool HandleBufferMapAsync(const char** commands, size_t* size) {
//* These requests are just forwarded to the buffer, with userdata containing what the client
//* will require in the return command.
const auto* cmd = GetCommand<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 @@ namespace dawn_wire {
auto* data = new MapUserdata;
data->server = this;
data->bufferId = bufferId;
data->bufferSerial = buffer->serial;
data->buffer = ObjectHandle{bufferId, buffer->serial};
data->requestSerial = requestSerial;
data->size = requestSize;
data->isWrite = isWrite;
@@ -696,13 +591,15 @@ namespace dawn_wire {
}
bool HandleBufferUpdateMappedData(const char** commands, size_t* size) {
const auto* cmd = GetCommand<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 @@ namespace dawn_wire {
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 @@ namespace dawn_wire {
if (data == nullptr) {
return false;
}
{% if type.name.CamelCase() in reverse_lookup_object_types %}
{% if type.name.CamelCase() in server_reverse_lookup_objects %}
m{{type.name.CamelCase()}}IdTable.Remove(data->handle);
{% endif %}

93
generator/wire_cmd.py Normal file
View File

@@ -0,0 +1,93 @@
# Copyright 2019 The Dawn Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import namedtuple
from common import Name
import common
def concat_names(*names):
return ' '.join([name.canonical_case() for name in names])
# Create wire commands from api methods
def compute_wire_params(api_params, wire_json):
wire_params = api_params.copy()
types = wire_params['types']
commands = []
return_commands = []
object_result_member = common.RecordMember(Name('result'), types['ObjectHandle'], 'value', False, True)
string_message_member = common.RecordMember(Name('message'), types['char'], 'const*', False, False)
string_message_member.length = 'strlen'
built_object_member = common.RecordMember(Name('built object'), types['ObjectHandle'], 'value', False, False)
callback_status_member = common.RecordMember(Name('status'), types['uint32_t'], 'value', False, False)
# Generate commands from object methods
for api_object in wire_params['by_category']['object']:
for method in api_object.methods:
if method.return_type.category != 'object' and method.return_type.name.canonical_case() != 'void':
# No other return types supported
continue
# Create object method commands by prepending "self"
members = [common.RecordMember(Name('self'), types[api_object.dict_name], 'value', False, False)]
members += method.arguments
# Client->Server commands that return an object return the result object handle
if method.return_type.category == 'object':
members.append(object_result_member)
command_name = concat_names(api_object.name, method.name)
command = common.Command(command_name, members)
command.derived_object = api_object
command.derived_method = method
commands.append(command)
# Create builder return ErrorCallback commands
# This can be removed when WebGPU error handling is implemented
if api_object.is_builder:
command_name = concat_names(api_object.name, Name('error callback'))
command = common.Command(command_name, [
built_object_member,
callback_status_member,
string_message_member,
])
command.derived_object = api_object
return_commands.append(command)
for (name, json_data) in wire_json['commands'].items():
commands.append(common.Command(name, common.linked_record_members(json_data, types)))
for (name, json_data) in wire_json['return commands'].items():
return_commands.append(common.Command(name, common.linked_record_members(json_data, types)))
wire_params['cmd_records'] = {
'command': commands,
'return command': return_commands
}
for commands in wire_params['cmd_records'].values():
for command in commands:
command.update_metadata()
commands.sort(key=lambda c: c.name.canonical_case())
wire_params.update(wire_json.get('special items', {}))
return wire_params