|
|
|
#ifndef GOOGLE_PROTOBUF_RUST_CPP_KERNEL_MAP_H__
|
|
|
|
#define GOOGLE_PROTOBUF_RUST_CPP_KERNEL_MAP_H__
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
#include <type_traits>
|
|
|
|
|
Rust: cut down on the amount of generated C++ code needed for maps
With the C++ kernel for Rust, we currently need to generate quite a few C++
thunks for operations on map fields. For each message we generate, we generate
these thunks for all possible map types that could have that message as a
value. These operations are for things such as insertion, removal, clearing,
iterating, etc.
The reason we do this is that templated types don't play well with FFI, so we
effectively need separate FFI endpoints for every possible combination of key
and value types used (or even potentially used) as a map field.
This CL fixes the problem by replacing the generated thunks with functions in
the runtime that can operate on `proto2::MessageLite*` without needing to care
about the specific message type.
The way it works is that we implement the operations using either
`UntypedMapBase` (the base class of all map types, which knows nothing about
the key and value types) or `KeyMapBase`, which knows the key type but not the
value type. I roughly followed the example of the table-driven parser, which
has a similar problem of needing to operate generically on maps without having
access to the concrete types.
I removed 54 thunks per message (that's 6 key types times 9 operations per
key), but had to add two new thunks per message:
- The `size_info` thunk looks up the `MapNodeSizeInfoT`, which is stored in a
small constant table. The important thing here is an offset indicating where
to look for the value in each map entry. This offset can be different for
every pair of key and value types, but we can safely assume that the result
does not depend on the signedness of the key. As a result we only need to
store four entries per message: one each for i32, i64, bool, and string.
- The `placement_new` thunk move-constructs a message in place. We need this
to be able to efficiently implement map insertion.
There are two big things that this CL does not address yet but which I plan to
follow up on:
- Enums still generate many map-related C++ thunks that could be replaced with
a common implementation. This should actually be much easier to handle than
messages, because every enum has the same representation as an i32.
- We still generate six `ProxiedInMapValue` implementations for every message,
but it should be possible to replace these with a blanket implementation that
works for all message types.
PiperOrigin-RevId: 657681421
4 months ago
|
|
|
#include "google/protobuf/map.h"
|
|
|
|
#include "google/protobuf/message_lite.h"
|
|
|
|
#include "rust/cpp_kernel/strings.h"
|
|
|
|
|
|
|
|
namespace google {
|
|
|
|
namespace protobuf {
|
|
|
|
namespace rust {
|
|
|
|
|
|
|
|
// String and bytes values are passed across the FFI boundary as owned raw
|
|
|
|
// pointers when we do map insertions. Unlike other types, they have to be
|
|
|
|
// explicitly deleted. This MakeCleanup() helper does nothing by default, but
|
|
|
|
// for std::string pointers it returns a std::unique_ptr to take ownership of
|
|
|
|
// the raw pointer.
|
|
|
|
template <typename T>
|
|
|
|
auto MakeCleanup(T value) {
|
|
|
|
if constexpr (std::is_same<T, std::string*>::value) {
|
|
|
|
return std::unique_ptr<std::string>(value);
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace rust
|
|
|
|
} // namespace protobuf
|
|
|
|
} // namespace google
|
|
|
|
|
|
|
|
// Defines concrete thunks to access typed map methods from Rust.
|
|
|
|
#define __PB_RUST_EXPOSE_SCALAR_MAP_METHODS( \
|
|
|
|
key_ty, rust_key_ty, ffi_key_ty, to_cpp_key, to_ffi_key, value_ty, \
|
|
|
|
rust_value_ty, ffi_view_ty, ffi_value_ty, to_cpp_value, to_ffi_value) \
|
|
|
|
google::protobuf::Map<key_ty, value_ty>* \
|
|
|
|
proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_new() { \
|
|
|
|
return new google::protobuf::Map<key_ty, value_ty>(); \
|
|
|
|
} \
|
|
|
|
void proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_free( \
|
|
|
|
google::protobuf::Map<key_ty, value_ty>* m) { \
|
|
|
|
delete m; \
|
|
|
|
} \
|
|
|
|
void proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_clear( \
|
|
|
|
google::protobuf::Map<key_ty, value_ty>* m) { \
|
|
|
|
m->clear(); \
|
|
|
|
} \
|
|
|
|
size_t proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_size( \
|
|
|
|
const google::protobuf::Map<key_ty, value_ty>* m) { \
|
|
|
|
return m->size(); \
|
|
|
|
} \
|
|
|
|
bool proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_insert( \
|
|
|
|
google::protobuf::Map<key_ty, value_ty>* m, ffi_key_ty key, ffi_value_ty value) { \
|
|
|
|
auto cleanup = google::protobuf::rust::MakeCleanup(value); \
|
|
|
|
(void)cleanup; \
|
|
|
|
auto iter_and_inserted = m->try_emplace(to_cpp_key, to_cpp_value); \
|
|
|
|
if (!iter_and_inserted.second) { \
|
|
|
|
iter_and_inserted.first->second = to_cpp_value; \
|
|
|
|
} \
|
|
|
|
return iter_and_inserted.second; \
|
|
|
|
} \
|
|
|
|
bool proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_get( \
|
|
|
|
const google::protobuf::Map<key_ty, value_ty>* m, ffi_key_ty key, \
|
|
|
|
ffi_view_ty* value) { \
|
|
|
|
auto cpp_key = to_cpp_key; \
|
|
|
|
auto it = m->find(cpp_key); \
|
|
|
|
if (it == m->end()) { \
|
|
|
|
return false; \
|
|
|
|
} \
|
|
|
|
auto& cpp_value = it->second; \
|
|
|
|
*value = to_ffi_value; \
|
|
|
|
return true; \
|
|
|
|
} \
|
|
|
|
google::protobuf::internal::UntypedMapIterator \
|
|
|
|
proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_iter( \
|
|
|
|
const google::protobuf::Map<key_ty, value_ty>* m) { \
|
|
|
|
return google::protobuf::internal::UntypedMapIterator::FromTyped(m->cbegin()); \
|
|
|
|
} \
|
|
|
|
void proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_iter_get( \
|
Rust: cut down on the amount of generated C++ code needed for maps
With the C++ kernel for Rust, we currently need to generate quite a few C++
thunks for operations on map fields. For each message we generate, we generate
these thunks for all possible map types that could have that message as a
value. These operations are for things such as insertion, removal, clearing,
iterating, etc.
The reason we do this is that templated types don't play well with FFI, so we
effectively need separate FFI endpoints for every possible combination of key
and value types used (or even potentially used) as a map field.
This CL fixes the problem by replacing the generated thunks with functions in
the runtime that can operate on `proto2::MessageLite*` without needing to care
about the specific message type.
The way it works is that we implement the operations using either
`UntypedMapBase` (the base class of all map types, which knows nothing about
the key and value types) or `KeyMapBase`, which knows the key type but not the
value type. I roughly followed the example of the table-driven parser, which
has a similar problem of needing to operate generically on maps without having
access to the concrete types.
I removed 54 thunks per message (that's 6 key types times 9 operations per
key), but had to add two new thunks per message:
- The `size_info` thunk looks up the `MapNodeSizeInfoT`, which is stored in a
small constant table. The important thing here is an offset indicating where
to look for the value in each map entry. This offset can be different for
every pair of key and value types, but we can safely assume that the result
does not depend on the signedness of the key. As a result we only need to
store four entries per message: one each for i32, i64, bool, and string.
- The `placement_new` thunk move-constructs a message in place. We need this
to be able to efficiently implement map insertion.
There are two big things that this CL does not address yet but which I plan to
follow up on:
- Enums still generate many map-related C++ thunks that could be replaced with
a common implementation. This should actually be much easier to handle than
messages, because every enum has the same representation as an i32.
- We still generate six `ProxiedInMapValue` implementations for every message,
but it should be possible to replace these with a blanket implementation that
works for all message types.
PiperOrigin-RevId: 657681421
4 months ago
|
|
|
const google::protobuf::internal::UntypedMapIterator* iter, int32_t, \
|
|
|
|
ffi_key_ty* key, ffi_view_ty* value) { \
|
|
|
|
auto typed_iter = \
|
|
|
|
iter->ToTyped<google::protobuf::Map<key_ty, value_ty>::const_iterator>(); \
|
|
|
|
const auto& cpp_key = typed_iter->first; \
|
|
|
|
const auto& cpp_value = typed_iter->second; \
|
|
|
|
*key = to_ffi_key; \
|
|
|
|
*value = to_ffi_value; \
|
|
|
|
} \
|
|
|
|
bool proto2_rust_thunk_Map_##rust_key_ty##_##rust_value_ty##_remove( \
|
|
|
|
google::protobuf::Map<key_ty, value_ty>* m, ffi_key_ty key, ffi_view_ty* value) { \
|
|
|
|
auto cpp_key = to_cpp_key; \
|
|
|
|
auto num_removed = m->erase(cpp_key); \
|
|
|
|
return num_removed > 0; \
|
|
|
|
}
|
|
|
|
|
|
|
|
// Defines the map thunks for all supported key types for a given value type.
|
|
|
|
#define __PB_RUST_EXPOSE_SCALAR_MAP_METHODS_FOR_VALUE_TYPE( \
|
|
|
|
value_ty, rust_value_ty, ffi_view_ty, ffi_value_ty, to_cpp_value, \
|
|
|
|
to_ffi_value) \
|
|
|
|
__PB_RUST_EXPOSE_SCALAR_MAP_METHODS( \
|
|
|
|
int32_t, i32, int32_t, key, cpp_key, value_ty, rust_value_ty, \
|
|
|
|
ffi_view_ty, ffi_value_ty, to_cpp_value, to_ffi_value); \
|
|
|
|
__PB_RUST_EXPOSE_SCALAR_MAP_METHODS( \
|
|
|
|
uint32_t, u32, uint32_t, key, cpp_key, value_ty, rust_value_ty, \
|
|
|
|
ffi_view_ty, ffi_value_ty, to_cpp_value, to_ffi_value); \
|
|
|
|
__PB_RUST_EXPOSE_SCALAR_MAP_METHODS( \
|
|
|
|
bool, bool, bool, key, cpp_key, value_ty, rust_value_ty, ffi_view_ty, \
|
|
|
|
ffi_value_ty, to_cpp_value, to_ffi_value); \
|
|
|
|
__PB_RUST_EXPOSE_SCALAR_MAP_METHODS( \
|
|
|
|
uint64_t, u64, uint64_t, key, cpp_key, value_ty, rust_value_ty, \
|
|
|
|
ffi_view_ty, ffi_value_ty, to_cpp_value, to_ffi_value); \
|
|
|
|
__PB_RUST_EXPOSE_SCALAR_MAP_METHODS( \
|
|
|
|
int64_t, i64, int64_t, key, cpp_key, value_ty, rust_value_ty, \
|
|
|
|
ffi_view_ty, ffi_value_ty, to_cpp_value, to_ffi_value); \
|
|
|
|
__PB_RUST_EXPOSE_SCALAR_MAP_METHODS( \
|
|
|
|
std::string, ProtoString, google::protobuf::rust::PtrAndLen, \
|
|
|
|
std::string(key.ptr, key.len), \
|
|
|
|
google::protobuf::rust::PtrAndLen(cpp_key.data(), cpp_key.size()), value_ty, \
|
|
|
|
rust_value_ty, ffi_view_ty, ffi_value_ty, to_cpp_value, to_ffi_value);
|
|
|
|
|
|
|
|
#endif // GOOGLE_PROTOBUF_RUST_CPP_KERNEL_MAP_H__
|