From 224e9343d21678b2bb861c7e6ced364c8a40406b Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Wed, 3 Jul 2024 13:27:11 -0700 Subject: [PATCH] Implement `operator std::shared_ptr &()`, remove 2 BAKEIN_BREAK: pass_shmp, pass_shcp --- include/pybind11/cast.h | 6 +- .../detail/smart_holder_type_caster_support.h | 258 ++++++++++++++++++ tests/test_class_sh_basic.py | 4 +- 3 files changed, 263 insertions(+), 5 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index cdbdad5d8..0aa75929e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -878,7 +878,7 @@ public: explicit operator std::shared_ptr &() { if (typeinfo->default_holder) { - throw std::runtime_error("BAKEIN_WIP: operator std::shared_ptr &()"); + shared_ptr_holder = sh_load_helper.loaded_as_shared_ptr(); } return shared_ptr_holder; } @@ -909,7 +909,7 @@ protected: } bool load_value_smart_holder(const value_and_holder &v_h) { - loaded_v_h = v_h; + sh_load_helper.loaded_v_h = v_h; return true; } @@ -947,7 +947,7 @@ protected: static bool try_direct_conversions(handle) { return false; } std::shared_ptr shared_ptr_holder; - value_and_holder loaded_v_h; + smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl }; /// Specialize for the common std::shared_ptr, so users don't need to diff --git a/include/pybind11/detail/smart_holder_type_caster_support.h b/include/pybind11/detail/smart_holder_type_caster_support.h index 42f48d5ca..29c790626 100644 --- a/include/pybind11/detail/smart_holder_type_caster_support.h +++ b/include/pybind11/detail/smart_holder_type_caster_support.h @@ -1,5 +1,6 @@ #pragma once +#include "../gil.h" #include "../pytypes.h" #include "../trampoline_self_life_support.h" #include "common.h" @@ -191,6 +192,263 @@ handle shared_ptr_to_python(const std::shared_ptr &shd_ptr, return type_caster_base::cast_holder(ptr, &shd_ptr); } +struct shared_ptr_parent_life_support { + PyObject *parent; + explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} { + Py_INCREF(parent); + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void operator()(void *) { + gil_scoped_acquire gil; + Py_DECREF(parent); + } +}; + +struct shared_ptr_trampoline_self_life_support { + PyObject *self; + explicit shared_ptr_trampoline_self_life_support(instance *inst) + : self{reinterpret_cast(inst)} { + gil_scoped_acquire gil; + Py_INCREF(self); + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void operator()(void *) { + gil_scoped_acquire gil; + Py_DECREF(self); + } +}; + +template ::value, int>::type = 0> +inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { + if (deleter == nullptr) { + return std::unique_ptr(raw_ptr); + } + return std::unique_ptr(raw_ptr, std::move(*deleter)); +} + +template ::value, int>::type = 0> +inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { + if (deleter == nullptr) { + pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no" + " instance available to return."); + } + return std::unique_ptr(raw_ptr, std::move(*deleter)); +} + +template +struct load_helper { + using holder_type = pybindit::memory::smart_holder; + + value_and_holder loaded_v_h; + + T *loaded_as_raw_ptr_unowned() const { + void *void_ptr = nullptr; + if (have_holder()) { + throw_if_uninitialized_or_disowned_holder(typeid(T)); + void_ptr = holder().template as_raw_ptr_unowned(); + } else if (loaded_v_h.vh != nullptr) { + void_ptr = loaded_v_h.value_ptr(); + } + if (void_ptr == nullptr) { + return nullptr; + } + return convert_type(void_ptr); + } + + T &loaded_as_lvalue_ref() const { + T *raw_ptr = loaded_as_raw_ptr_unowned(); + if (raw_ptr == nullptr) { + throw reference_cast_error(); + } + return *raw_ptr; + } + + std::shared_ptr make_shared_ptr_with_responsible_parent(handle parent) const { + return std::shared_ptr(loaded_as_raw_ptr_unowned(), + shared_ptr_parent_life_support(parent.ptr())); + } + + std::shared_ptr loaded_as_shared_ptr(handle responsible_parent = nullptr) const { + if (!have_holder()) { + return nullptr; + } + throw_if_uninitialized_or_disowned_holder(typeid(T)); + holder_type &hld = holder(); + hld.ensure_is_not_disowned("loaded_as_shared_ptr"); + if (hld.vptr_is_using_noop_deleter) { + if (responsible_parent) { + return make_shared_ptr_with_responsible_parent(responsible_parent); + } + throw std::runtime_error("Non-owning holder (loaded_as_shared_ptr)."); + } + auto *void_raw_ptr = hld.template as_raw_ptr_unowned(); + auto *type_raw_ptr = convert_type(void_raw_ptr); + if (hld.pointee_depends_on_holder_owner) { + auto *vptr_gd_ptr = std::get_deleter(hld.vptr); + if (vptr_gd_ptr != nullptr) { + std::shared_ptr released_ptr = vptr_gd_ptr->released_ptr.lock(); + if (released_ptr) { + return std::shared_ptr(released_ptr, type_raw_ptr); + } + std::shared_ptr to_be_released( + type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); + vptr_gd_ptr->released_ptr = to_be_released; + return to_be_released; + } + auto *sptsls_ptr = std::get_deleter(hld.vptr); + if (sptsls_ptr != nullptr) { + // This code is reachable only if there are multiple registered_instances for the + // same pointee. + if (reinterpret_cast(loaded_v_h.inst) == sptsls_ptr->self) { + pybind11_fail( + "ssmart_holder_type_caster_support loaded_as_shared_ptr failure: " + "loaded_v_h.inst == sptsls_ptr->self"); + } + } + if (sptsls_ptr != nullptr + || !pybindit::memory::type_has_shared_from_this(type_raw_ptr)) { + return std::shared_ptr( + type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); + } + if (hld.vptr_is_external_shared_ptr) { + pybind11_fail("smart_holder_type_casters loaded_as_shared_ptr failure: not " + "implemented: trampoline-self-life-support for external shared_ptr " + "to type inheriting from std::enable_shared_from_this."); + } + pybind11_fail("smart_holder_type_casters: loaded_as_shared_ptr failure: internal " + "inconsistency."); + } + std::shared_ptr void_shd_ptr = hld.template as_shared_ptr(); + return std::shared_ptr(void_shd_ptr, type_raw_ptr); + } + + template + std::unique_ptr loaded_as_unique_ptr(const char *context = "loaded_as_unique_ptr") { + if (!have_holder()) { + return unique_with_deleter(nullptr, std::unique_ptr()); + } + throw_if_uninitialized_or_disowned_holder(typeid(T)); + throw_if_instance_is_currently_owned_by_shared_ptr(); + holder().ensure_is_not_disowned(context); + holder().template ensure_compatible_rtti_uqp_del(context); + holder().ensure_use_count_1(context); + auto raw_void_ptr = holder().template as_raw_ptr_unowned(); + + void *value_void_ptr = loaded_v_h.value_ptr(); + if (value_void_ptr != raw_void_ptr) { + pybind11_fail("smart_holder_type_casters: loaded_as_unique_ptr failure:" + " value_void_ptr != raw_void_ptr"); + } + + // SMART_HOLDER_WIP: MISSING: Safety checks for type conversions + // (T must be polymorphic or meet certain other conditions). + T *raw_type_ptr = convert_type(raw_void_ptr); + + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible(raw_type_ptr); + if (self_life_support == nullptr && holder().pointee_depends_on_holder_owner) { + throw value_error("Alias class (also known as trampoline) does not inherit from " + "py::trampoline_self_life_support, therefore the ownership of this " + "instance cannot safely be transferred to C++."); + } + + // Temporary variable to store the extracted deleter in. + std::unique_ptr extracted_deleter; + + auto *gd = std::get_deleter(holder().vptr); + if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del() call above. + // In smart_holder_poc, a custom deleter is always stored in a guarded delete. + // The guarded delete's std::function actually points at the + // custom_deleter type, so we can verify it is of the custom deleter type and + // finally extract its deleter. + using custom_deleter_D = pybindit::memory::custom_deleter; + const auto &custom_deleter_ptr = gd->del_fun.template target(); + assert(custom_deleter_ptr != nullptr); + // Now that we have confirmed the type of the deleter matches the desired return + // value we can extract the function. + extracted_deleter = std::unique_ptr(new D(std::move(custom_deleter_ptr->deleter))); + } + + // Critical transfer-of-ownership section. This must stay together. + if (self_life_support != nullptr) { + holder().disown(); + } else { + holder().release_ownership(); + } + auto result = unique_with_deleter(raw_type_ptr, std::move(extracted_deleter)); + if (self_life_support != nullptr) { + self_life_support->activate_life_support(loaded_v_h); + } else { + loaded_v_h.value_ptr() = nullptr; + deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type); + } + // Critical section end. + + return result; + } + +#ifdef BAKEIN_WIP // Is this needed? shared_ptr_from_python(responsible_parent) + // This function will succeed even if the `responsible_parent` does not own the + // wrapped C++ object directly. + // It is the responsibility of the caller to ensure that the `responsible_parent` + // has a `keep_alive` relationship with the owner of the wrapped C++ object, or + // that the wrapped C++ object lives for the duration of the process. + static std::shared_ptr shared_ptr_from_python(handle responsible_parent) { + smart_holder_type_caster_load loader; + loader.load(responsible_parent, false); + return loader.loaded_as_shared_ptr(responsible_parent); + } +#endif + +private: + bool have_holder() const { + return loaded_v_h.vh != nullptr && loaded_v_h.holder_constructed(); + } + + holder_type &holder() const { return loaded_v_h.holder(); } + + // have_holder() must be true or this function will fail. + void throw_if_uninitialized_or_disowned_holder(const char *typeid_name) const { + static const std::string missing_value_msg = "Missing value for wrapped C++ type `"; + if (!holder().is_populated) { + throw value_error(missing_value_msg + clean_type_id(typeid_name) + + "`: Python instance is uninitialized."); + } + if (!holder().has_pointee()) { + throw value_error(missing_value_msg + clean_type_id(typeid_name) + + "`: Python instance was disowned."); + } + } + + void throw_if_uninitialized_or_disowned_holder(const std::type_info &type_info) const { + throw_if_uninitialized_or_disowned_holder(type_info.name()); + } + + // have_holder() must be true or this function will fail. + void throw_if_instance_is_currently_owned_by_shared_ptr() const { + auto vptr_gd_ptr = std::get_deleter(holder().vptr); + if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) { + throw value_error("Python instance is currently owned by a std::shared_ptr."); + } + } + + T *convert_type(void *void_ptr) const { +#ifdef BAKEIN_WIP // Is this needed? implicit_casts + if (void_ptr != nullptr && load_impl.loaded_v_h_cpptype != nullptr + && !load_impl.reinterpret_cast_deemed_ok && !load_impl.implicit_casts.empty()) { + for (auto implicit_cast : load_impl.implicit_casts) { + void_ptr = implicit_cast(void_ptr); + } + } +#endif + return static_cast(void_ptr); + } +}; + PYBIND11_NAMESPACE_END(smart_holder_type_caster_support) PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/tests/test_class_sh_basic.py b/tests/test_class_sh_basic.py index 48fa4f6b3..8f44b90b6 100644 --- a/tests/test_class_sh_basic.py +++ b/tests/test_class_sh_basic.py @@ -46,8 +46,8 @@ def test_cast(rtrn_f, expected): (m.pass_mref, "Mref", "pass_mref:Mref(_MvCtor)*_MvCtor"), (m.pass_cptr, "Cptr", "pass_cptr:Cptr(_MvCtor)*_MvCtor"), (m.pass_mptr, "Mptr", "pass_mptr:Mptr(_MvCtor)*_MvCtor"), - # BAKEIN_BREAK (m.pass_shmp, "Shmp", "pass_shmp:Shmp(_MvCtor)*_MvCtor"), - # BAKEIN_BREAK (m.pass_shcp, "Shcp", "pass_shcp:Shcp(_MvCtor)*_MvCtor"), + (m.pass_shmp, "Shmp", "pass_shmp:Shmp(_MvCtor)*_MvCtor"), + (m.pass_shcp, "Shcp", "pass_shcp:Shcp(_MvCtor)*_MvCtor"), # BAKEIN_BREAK (m.pass_uqmp, "Uqmp", "pass_uqmp:Uqmp(_MvCtor)*_MvCtor"), # BAKEIN_BREAK (m.pass_uqcp, "Uqcp", "pass_uqcp:Uqcp(_MvCtor)*_MvCtor"), ],