From 70ba91f721d4f6d36dd5d5e05b430f8bfa1df46b Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 6 Jul 2024 10:49:22 -0700 Subject: [PATCH] Inline smart_holder_type_caster_support, trampoline_self_life_support in type_caster_base.h: the organization of the existing code makes it very difficult to factor the added code properly. --- CMakeLists.txt | 2 - include/pybind11/cast.h | 1 - .../detail/smart_holder_type_caster_support.h | 394 --------------- .../smart_holder_value_and_holder_support.h | 59 --- include/pybind11/detail/type_caster_base.h | 460 +++++++++++++++++- .../pybind11/trampoline_self_life_support.h | 57 +-- tests/extra_python_package/test_files.py | 2 - 7 files changed, 457 insertions(+), 518 deletions(-) delete mode 100644 include/pybind11/detail/smart_holder_type_caster_support.h delete mode 100644 include/pybind11/detail/smart_holder_value_and_holder_support.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 552b94f18..7e43bcdd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,8 +154,6 @@ set(PYBIND11_HEADERS include/pybind11/detail/init.h include/pybind11/detail/internals.h include/pybind11/detail/smart_holder_poc.h - include/pybind11/detail/smart_holder_type_caster_support.h - include/pybind11/detail/smart_holder_value_and_holder_support.h include/pybind11/detail/type_caster_base.h include/pybind11/detail/typeid.h include/pybind11/attr.h diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 8a2b096da..1bdd9a483 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -12,7 +12,6 @@ #include "detail/common.h" #include "detail/descr.h" -#include "detail/smart_holder_type_caster_support.h" #include "detail/type_caster_base.h" #include "detail/typeid.h" #include "pytypes.h" diff --git a/include/pybind11/detail/smart_holder_type_caster_support.h b/include/pybind11/detail/smart_holder_type_caster_support.h deleted file mode 100644 index 0355c11fa..000000000 --- a/include/pybind11/detail/smart_holder_type_caster_support.h +++ /dev/null @@ -1,394 +0,0 @@ -#pragma once - -// BAKEIN_WIP: IWYU cleanup -#include "../gil.h" -#include "../pytypes.h" -#include "../trampoline_self_life_support.h" -#include "common.h" -#include "dynamic_raw_ptr_cast_if_possible.h" -#include "internals.h" -#include "smart_holder_value_and_holder_support.h" -#include "typeid.h" - -#include -#include -#include -#include -#include -#include - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) -PYBIND11_NAMESPACE_BEGIN(detail) -PYBIND11_NAMESPACE_BEGIN(smart_holder_type_caster_support) - -template -handle smart_holder_from_unique_ptr(std::unique_ptr &&src, - return_value_policy policy, - handle parent, - const std::pair &st) { - if (policy != return_value_policy::automatic - && policy != return_value_policy::automatic_reference - && policy != return_value_policy::reference_internal - && policy != return_value_policy::move) { - // SMART_HOLDER_WIP: IMPROVABLE: Error message. - throw cast_error("Invalid return_value_policy for unique_ptr."); - } - if (!src) { - return none().release(); - } - void *src_raw_void_ptr = const_cast(st.first); - assert(st.second != nullptr); - const detail::type_info *tinfo = st.second; - if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { - auto *self_life_support - = dynamic_raw_ptr_cast_if_possible(src.get()); - if (self_life_support != nullptr) { - value_and_holder &v_h = self_life_support->v_h; - if (v_h.inst != nullptr && v_h.vh != nullptr) { - auto &holder = v_h.holder(); - if (!holder.is_disowned) { - pybind11_fail("smart_holder_from_unique_ptr: unexpected " - "smart_holder.is_disowned failure."); - } - // Critical transfer-of-ownership section. This must stay together. - self_life_support->deactivate_life_support(); - holder.reclaim_disowned(); - (void) src.release(); - // Critical section end. - return existing_inst; - } - } - throw cast_error("Invalid unique_ptr: another instance owns this pointer already."); - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); - inst_raw_ptr->owned = true; - void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); - valueptr = src_raw_void_ptr; - - if (static_cast(src.get()) == src_raw_void_ptr) { - // This is a multiple-inheritance situation that is incompatible with the current - // shared_from_this handling (see PR #3023). - // SMART_HOLDER_WIP: IMPROVABLE: Is there a better solution? - src_raw_void_ptr = nullptr; - } - auto smhldr - = pybindit::memory::smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr); - tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); - - if (policy == return_value_policy::reference_internal) { - keep_alive_impl(inst, parent); - } - - return inst.release(); -} - -template -handle smart_holder_from_unique_ptr(std::unique_ptr &&src, - return_value_policy policy, - handle parent, - const std::pair &st) { - return smart_holder_from_unique_ptr( - std::unique_ptr(const_cast(src.release()), - std::move(src.get_deleter())), // Const2Mutbl - policy, - parent, - st); -} - -template -handle smart_holder_from_shared_ptr(const std::shared_ptr &src, - return_value_policy policy, - handle parent, - const std::pair &st) { - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::automatic_reference: - break; - case return_value_policy::take_ownership: - throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership)."); - case return_value_policy::copy: - case return_value_policy::move: - break; - case return_value_policy::reference: - throw cast_error("Invalid return_value_policy for shared_ptr (reference)."); - case return_value_policy::reference_internal: - break; - } - if (!src) { - return none().release(); - } - - auto src_raw_ptr = src.get(); - assert(st.second != nullptr); - void *src_raw_void_ptr = static_cast(src_raw_ptr); - const detail::type_info *tinfo = st.second; - if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { - // SMART_HOLDER_WIP: MISSING: Enforcement of consistency with existing smart_holder. - // SMART_HOLDER_WIP: MISSING: keep_alive. - return existing_inst; - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); - inst_raw_ptr->owned = true; - void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); - valueptr = src_raw_void_ptr; - - auto smhldr = pybindit::memory::smart_holder::from_shared_ptr( - std::shared_ptr(src, const_cast(st.first))); - tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); - - if (policy == return_value_policy::reference_internal) { - keep_alive_impl(inst, parent); - } - - return inst.release(); -} - -template -handle smart_holder_from_shared_ptr(const std::shared_ptr &src, - return_value_policy policy, - handle parent, - const std::pair &st) { - return smart_holder_from_shared_ptr(std::const_pointer_cast(src), // Const2Mutbl - policy, - parent, - st); -} - -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 - : smart_holder_value_and_holder_support::value_and_holder_helper { - using holder_type = pybindit::memory::smart_holder; - - value_and_holder loaded_v_h; - - load_helper() - : smart_holder_value_and_holder_support::value_and_holder_helper( - &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: - 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/include/pybind11/detail/smart_holder_value_and_holder_support.h b/include/pybind11/detail/smart_holder_value_and_holder_support.h deleted file mode 100644 index 6c4469de8..000000000 --- a/include/pybind11/detail/smart_holder_value_and_holder_support.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -// BAKEIN_WIP: IWYU cleanup -#include "common.h" -#include "smart_holder_poc.h" -#include "typeid.h" - -#include -#include -#include - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) -PYBIND11_NAMESPACE_BEGIN(detail) -PYBIND11_NAMESPACE_BEGIN(smart_holder_value_and_holder_support) - -// BAKEIN_WIP: Factor out `struct value_and_holder` from type_caster_base.h, -// then this helper does not have to be templated. -template -struct value_and_holder_helper { - const VHType *loaded_v_h; - - explicit value_and_holder_helper(const VHType *loaded_v_h) : loaded_v_h{loaded_v_h} {} - - bool have_holder() const { - return loaded_v_h->vh != nullptr && loaded_v_h->holder_constructed(); - } - - pybindit::memory::smart_holder &holder() const { - return loaded_v_h->template holder(); - } - - 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."); - } - } -}; - -PYBIND11_NAMESPACE_END(smart_holder_value_and_holder_support) -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index edeffa8fa..c3fa8fa34 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -9,12 +9,13 @@ #pragma once +#include "../gil.h" #include "../pytypes.h" #include "common.h" #include "descr.h" +#include "dynamic_raw_ptr_cast_if_possible.h" #include "internals.h" #include "smart_holder_poc.h" -#include "smart_holder_value_and_holder_support.h" #include "typeid.h" #include @@ -530,6 +531,455 @@ inline PyThreadState *get_thread_state_unchecked() { void keep_alive_impl(handle nurse, handle patient); inline PyObject *make_new_instance(PyTypeObject *type); +// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. +inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); + +PYBIND11_NAMESPACE_END(detail) + +// The original core idea for this struct goes back to PyCLIF: +// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37 +// URL provided here mainly to give proper credit. To fully explain the `HoldPyObj` feature, more +// context is needed (SMART_HOLDER_WIP). +struct trampoline_self_life_support { + detail::value_and_holder v_h; + + trampoline_self_life_support() = default; + + void activate_life_support(const detail::value_and_holder &v_h_) { + Py_INCREF((PyObject *) v_h_.inst); + v_h = v_h_; + } + + void deactivate_life_support() { + Py_DECREF((PyObject *) v_h.inst); + v_h = detail::value_and_holder(); + } + + ~trampoline_self_life_support() { + if (v_h.inst != nullptr && v_h.vh != nullptr) { + void *value_void_ptr = v_h.value_ptr(); + if (value_void_ptr != nullptr) { + PyGILState_STATE threadstate = PyGILState_Ensure(); + v_h.value_ptr() = nullptr; + v_h.holder().release_disowned(); + detail::deregister_instance(v_h.inst, value_void_ptr, v_h.type); + Py_DECREF((PyObject *) v_h.inst); // Must be after deregister. + PyGILState_Release(threadstate); + } + } + } + + // For the next two, the default implementations generate undefined behavior (ASAN failures + // manually verified). The reason is that v_h needs to be kept default-initialized. + trampoline_self_life_support(const trampoline_self_life_support &) {} + trampoline_self_life_support(trampoline_self_life_support &&) noexcept {} + + // These should never be needed (please provide test cases if you think they are). + trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete; + trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete; +}; + +PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NAMESPACE_BEGIN(smart_holder_type_caster_support) + +struct value_and_holder_helper { + value_and_holder loaded_v_h; + + bool have_holder() const { + return loaded_v_h.vh != nullptr && loaded_v_h.holder_constructed(); + } + + pybindit::memory::smart_holder &holder() const { + return loaded_v_h.holder(); + } + + 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."); + } + } +}; + +template +handle smart_holder_from_unique_ptr(std::unique_ptr &&src, + return_value_policy policy, + handle parent, + const std::pair &st) { + if (policy != return_value_policy::automatic + && policy != return_value_policy::automatic_reference + && policy != return_value_policy::reference_internal + && policy != return_value_policy::move) { + // SMART_HOLDER_WIP: IMPROVABLE: Error message. + throw cast_error("Invalid return_value_policy for unique_ptr."); + } + if (!src) { + return none().release(); + } + void *src_raw_void_ptr = const_cast(st.first); + assert(st.second != nullptr); + const detail::type_info *tinfo = st.second; + if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible(src.get()); + if (self_life_support != nullptr) { + value_and_holder &v_h = self_life_support->v_h; + if (v_h.inst != nullptr && v_h.vh != nullptr) { + auto &holder = v_h.holder(); + if (!holder.is_disowned) { + pybind11_fail("smart_holder_from_unique_ptr: unexpected " + "smart_holder.is_disowned failure."); + } + // Critical transfer-of-ownership section. This must stay together. + self_life_support->deactivate_life_support(); + holder.reclaim_disowned(); + (void) src.release(); + // Critical section end. + return existing_inst; + } + } + throw cast_error("Invalid unique_ptr: another instance owns this pointer already."); + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); + inst_raw_ptr->owned = true; + void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); + valueptr = src_raw_void_ptr; + + if (static_cast(src.get()) == src_raw_void_ptr) { + // This is a multiple-inheritance situation that is incompatible with the current + // shared_from_this handling (see PR #3023). + // SMART_HOLDER_WIP: IMPROVABLE: Is there a better solution? + src_raw_void_ptr = nullptr; + } + auto smhldr + = pybindit::memory::smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr); + tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); + + if (policy == return_value_policy::reference_internal) { + keep_alive_impl(inst, parent); + } + + return inst.release(); +} + +template +handle smart_holder_from_unique_ptr(std::unique_ptr &&src, + return_value_policy policy, + handle parent, + const std::pair &st) { + return smart_holder_from_unique_ptr( + std::unique_ptr(const_cast(src.release()), + std::move(src.get_deleter())), // Const2Mutbl + policy, + parent, + st); +} + +template +handle smart_holder_from_shared_ptr(const std::shared_ptr &src, + return_value_policy policy, + handle parent, + const std::pair &st) { + switch (policy) { + case return_value_policy::automatic: + case return_value_policy::automatic_reference: + break; + case return_value_policy::take_ownership: + throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership)."); + case return_value_policy::copy: + case return_value_policy::move: + break; + case return_value_policy::reference: + throw cast_error("Invalid return_value_policy for shared_ptr (reference)."); + case return_value_policy::reference_internal: + break; + } + if (!src) { + return none().release(); + } + + auto src_raw_ptr = src.get(); + assert(st.second != nullptr); + void *src_raw_void_ptr = static_cast(src_raw_ptr); + const detail::type_info *tinfo = st.second; + if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { + // SMART_HOLDER_WIP: MISSING: Enforcement of consistency with existing smart_holder. + // SMART_HOLDER_WIP: MISSING: keep_alive. + return existing_inst; + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); + inst_raw_ptr->owned = true; + void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); + valueptr = src_raw_void_ptr; + + auto smhldr = pybindit::memory::smart_holder::from_shared_ptr( + std::shared_ptr(src, const_cast(st.first))); + tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); + + if (policy == return_value_policy::reference_internal) { + keep_alive_impl(inst, parent); + } + + return inst.release(); +} + +template +handle smart_holder_from_shared_ptr(const std::shared_ptr &src, + return_value_policy policy, + handle parent, + const std::pair &st) { + return smart_holder_from_shared_ptr(std::const_pointer_cast(src), // Const2Mutbl + policy, + parent, + st); +} + +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 : value_and_holder_helper { + using holder_type = pybindit::memory::smart_holder; + + 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("smart_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: + 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) + class type_caster_generic { public: PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info) @@ -635,10 +1085,10 @@ public: // Base methods for generic caster; there are overridden in copyable_holder_caster void load_value(value_and_holder &&v_h) { if (typeinfo->default_holder) { - smart_holder_value_and_holder_support::value_and_holder_helper - vh_helper(&v_h); - if (vh_helper.have_holder()) { - vh_helper.throw_if_uninitialized_or_disowned_holder(typeid(cpptype)); + smart_holder_type_caster_support::value_and_holder_helper v_h_helper; + v_h_helper.loaded_v_h = v_h; + if (v_h_helper.have_holder()) { + v_h_helper.throw_if_uninitialized_or_disowned_holder(typeid(cpptype)); } } auto *&vptr = v_h.value_ptr(); diff --git a/include/pybind11/trampoline_self_life_support.h b/include/pybind11/trampoline_self_life_support.h index b7e1f12c4..ee809b9c5 100644 --- a/include/pybind11/trampoline_self_life_support.h +++ b/include/pybind11/trampoline_self_life_support.h @@ -1,61 +1,8 @@ -// Copyright (c) 2021 The Pybind Development Team. +// Copyright (c) 2024 The Pybind Development Team. // All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #pragma once -#include "detail/common.h" -#include "detail/smart_holder_poc.h" +// BAKEIN_WIP: Needs refactoring of existing pybind11 code. #include "detail/type_caster_base.h" - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -PYBIND11_NAMESPACE_BEGIN(detail) -// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); -PYBIND11_NAMESPACE_END(detail) - -// The original core idea for this struct goes back to PyCLIF: -// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37 -// URL provided here mainly to give proper credit. To fully explain the `HoldPyObj` feature, more -// context is needed (SMART_HOLDER_WIP). -struct trampoline_self_life_support { - detail::value_and_holder v_h; - - trampoline_self_life_support() = default; - - void activate_life_support(const detail::value_and_holder &v_h_) { - Py_INCREF((PyObject *) v_h_.inst); - v_h = v_h_; - } - - void deactivate_life_support() { - Py_DECREF((PyObject *) v_h.inst); - v_h = detail::value_and_holder(); - } - - ~trampoline_self_life_support() { - if (v_h.inst != nullptr && v_h.vh != nullptr) { - void *value_void_ptr = v_h.value_ptr(); - if (value_void_ptr != nullptr) { - PyGILState_STATE threadstate = PyGILState_Ensure(); - v_h.value_ptr() = nullptr; - v_h.holder().release_disowned(); - detail::deregister_instance(v_h.inst, value_void_ptr, v_h.type); - Py_DECREF((PyObject *) v_h.inst); // Must be after deregister. - PyGILState_Release(threadstate); - } - } - } - - // For the next two, the default implementations generate undefined behavior (ASAN failures - // manually verified). The reason is that v_h needs to be kept default-initialized. - trampoline_self_life_support(const trampoline_self_life_support &) {} - trampoline_self_life_support(trampoline_self_life_support &&) noexcept {} - - // These should never be needed (please provide test cases if you think they are). - trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete; - trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete; -}; - -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index ea03a7586..fb9885ceb 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -60,8 +60,6 @@ detail_headers = { "include/pybind11/detail/init.h", "include/pybind11/detail/internals.h", "include/pybind11/detail/smart_holder_poc.h", - "include/pybind11/detail/smart_holder_type_caster_support.h", - "include/pybind11/detail/smart_holder_value_and_holder_support.h", "include/pybind11/detail/type_caster_base.h", "include/pybind11/detail/typeid.h", }