Skip to content

Commit e9d7a6b

Browse files
rwgkcopybara-github
authored andcommitted
Introduce PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY (see proto_cast_util.h):
This macro can be defined by users certain about ABI compatibility between all Python extensions in their environment using protobufs. If defined, passing protos from Python to C++ may skip serialization/deserialization. — Note that this change makes the default behavior safer, but unless `PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY` is defined, the runtime performance may be noticably slower. PiperOrigin-RevId: 568608539
1 parent 3d7834b commit e9d7a6b

File tree

7 files changed

+82
-10
lines changed

7 files changed

+82
-10
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,12 @@ avoided, but not universally. Fundamentally, sharing C++ native protobuf
6464
objects between C++ and Python is unsafe because C++ assumes that it has
6565
exclusive ownership and may manipulate references in a way that undermines
6666
Python's much safer ownership semantics. Because of this, sharing mutable
67-
references or pointers between C++ and Python is not allowed. However, when
68-
passing a Python protobuf object to C++, the bindings may share the underlying
69-
C++ native protobuf object with C++ when passed by `const &` or `const *`.
67+
references or pointers between C++ and Python is not allowed.
68+
However, when passing a Python protobuf object to
69+
C++, and with `PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY` defined
70+
(see proto_cast_util.h),
71+
the bindings will share the underlying C++ native protobuf object with C++ when
72+
passed by `const &` or `const *`.
7073

7174
### Protobuf Extensions
7275

pybind11_protobuf/proto_cast_util.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,11 @@ void ImportProtoDescriptorModule(const Descriptor* descriptor) {
575575
}
576576

577577
const Message* PyProtoGetCppMessagePointer(py::handle src) {
578+
#if !defined(PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY)
579+
return nullptr;
580+
#else
581+
// Assume that C++ proto objects are compatible as long as there is a C++
582+
// message pointer.
578583
assert(PyGILState_Check());
579584
if (!GlobalState::instance()->py_proto_api()) return nullptr;
580585
auto* ptr =
@@ -583,9 +588,9 @@ const Message* PyProtoGetCppMessagePointer(py::handle src) {
583588
// Clear the type_error set by GetMessagePointer sets a type_error when
584589
// src was not a wrapped C++ proto message.
585590
PyErr_Clear();
586-
return nullptr;
587591
}
588592
return ptr;
593+
#endif
589594
}
590595

591596
absl::optional<std::string> PyProtoDescriptorName(py::handle py_proto) {

pybind11_protobuf/proto_cast_util.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
#include "google/protobuf/message.h"
1515
#include "absl/types/optional.h"
1616

17+
// PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY can be defined by users
18+
// certain about ABI compatibility between all Python extensions in their
19+
// environment using protobufs. If defined, passing protos from Python to C++
20+
// may skip serialization/deserialization.
21+
// Assuming full ABI compatibility means (roughly) that the following are
22+
// compatible between all involved Python extensions:
23+
// * Protobuf library versions.
24+
// * Compiler/linker & compiler/linker options.
25+
// #define PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY
26+
1727
namespace pybind11_protobuf {
1828

1929
// Initialize internal proto cast dependencies, which includes importing

pybind11_protobuf/proto_caster_impl.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,7 @@ struct proto_caster_load_impl<::google::protobuf::Message> {
9999
// Attempt to use the PyProto_API to get an underlying C++ message pointer
100100
// from the object.
101101
value = pybind11_protobuf::PyProtoGetCppMessagePointer(src);
102-
if (value && value->GetDescriptor() && value->GetDescriptor()->file() &&
103-
value->GetDescriptor()->file()->pool() ==
104-
::google::protobuf::DescriptorPool::generated_pool()) {
105-
// Only messages in the same generated_pool() can be referenced directly.
102+
if (value) {
106103
return true;
107104
}
108105

pybind11_protobuf/tests/BUILD

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Tests and examples for the pybind11_protobuf package.
22

3+
load("@com_github_grpc_grpc//bazel:python_rules.bzl", "py_proto_library")
4+
35
# Placeholder: load py_proto_library
46
# Placeholder: load py_library
5-
6-
load("@com_github_grpc_grpc//bazel:python_rules.bzl", "py_proto_library")
77
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
88
# [internal] load cc_proto_library.bzl
99

@@ -235,6 +235,16 @@ py_test(
235235
],
236236
)
237237

238+
# Externally this is currently only built but not used.
239+
pybind_extension(
240+
name = "very_large_proto_module",
241+
srcs = ["very_large_proto_module.cc"],
242+
deps = [
243+
"//pybind11_protobuf:native_proto_caster",
244+
"@com_google_protobuf//:protobuf",
245+
],
246+
)
247+
238248
# tests for wrapped_proto_caster
239249

240250
pybind_extension(
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2023 The Pybind Development Team. All rights reserved.
2+
//
3+
// All rights reserved. Use of this source code is governed by a
4+
// BSD-style license that can be found in the LICENSE file.
5+
6+
#include <pybind11/pybind11.h>
7+
8+
#include <cstddef>
9+
10+
#include "google/protobuf/message.h"
11+
#include "pybind11_protobuf/native_proto_caster.h"
12+
13+
PYBIND11_MODULE(very_large_proto_module, m) {
14+
pybind11_protobuf::ImportNativeProtoCasters();
15+
16+
m.def("get_space_used_estimate",
17+
[](const ::google::protobuf::Message& msg) -> std::size_t {
18+
const ::google::protobuf::Reflection* refl = msg.GetReflection();
19+
return refl != nullptr ? refl->SpaceUsedLong(msg) : 0;
20+
});
21+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright (c) 2023 The Pybind Development Team. All rights reserved.
2+
#
3+
# All rights reserved. Use of this source code is governed by a
4+
# BSD-style license that can be found in the LICENSE file.
5+
6+
from absl.testing import absltest
7+
8+
from pybind11_protobuf.tests import test_pb2
9+
from pybind11_protobuf.tests import very_large_proto_module as m
10+
11+
12+
class MessageTest(absltest.TestCase):
13+
14+
def test_greater_than_2gb_limit(self):
15+
# This test is expected to fail if the Python-to-C++ conversion involves
16+
# serialization/deserialization.
17+
# Code exercised: IsCProtoSuitableForCopyFromCall()
18+
kb = 1024
19+
msg_size = 2 * kb**3 + kb # A little over 2 GB.
20+
msg = test_pb2.TestMessage(string_value='x' * msg_size)
21+
space_used_estimate = m.get_space_used_estimate(msg)
22+
self.assertGreater(space_used_estimate, msg_size)
23+
24+
25+
if __name__ == '__main__':
26+
absltest.main()

0 commit comments

Comments
 (0)