Skip to content
Open
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ jobs:
if: runner.os != 'Windows'
run: echo "CMAKE_GENERATOR=Ninja" >> "$GITHUB_ENV"

# More-or-less randomly adding a few extra flags here
# More-or-less randomly adding a few extra flags here.
# In particular, use this one to smoke test the next ABI bump.
- name: Configure
run: >
cmake -S. -B.
Expand All @@ -213,6 +214,7 @@ jobs:
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=14
-DPYBIND11_INTERNALS_VERSION=10000000

# Checks to makes sure defining `_` is allowed
# Triggers EHsc missing error on Windows
Expand Down
6 changes: 5 additions & 1 deletion include/pybind11/detail/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,14 @@ extern "C" inline void pybind11_meta_dealloc(PyObject *obj) {
auto tindex = std::type_index(*tinfo->cpptype);
internals.direct_conversions.erase(tindex);

auto &local_internals = get_local_internals();
if (tinfo->module_local) {
get_local_internals().registered_types_cpp.erase(tindex);
local_internals.registered_types_cpp.erase(tinfo->cpptype);
} else {
internals.registered_types_cpp.erase(tindex);
#if PYBIND11_INTERNALS_VERSION >= 12
internals.registered_types_cpp_fast.erase(tinfo->cpptype);
#endif
}
internals.registered_types_py.erase(tinfo->type);

Expand Down
24 changes: 22 additions & 2 deletions include/pybind11/detail/internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
/// further ABI-incompatible changes may be made before the ABI is officially
/// changed to the new version.
#ifndef PYBIND11_INTERNALS_VERSION
// REMINDER for next version bump: remove loader_life_support_tls
# define PYBIND11_INTERNALS_VERSION 11
#endif

Expand Down Expand Up @@ -177,6 +176,12 @@ struct type_equal_to {
};
#endif

// For now, we don't bother adding a fancy hash for pointers and just
// let the standard library use the identity hash function if that's
// what it wants to do (e.g., as in libstdc++).
template <typename value_type>
using fast_type_map = std::unordered_map<const std::type_info *, value_type>;

template <typename value_type>
using type_map = std::unordered_map<std::type_index, value_type, type_hash, type_equal_to>;

Expand Down Expand Up @@ -237,6 +242,15 @@ struct internals {
pymutex mutex;
pymutex exception_translator_mutex;
#endif
#if PYBIND11_INTERNALS_VERSION >= 12
// non-normative but fast "hint" for registered_types_cpp. Meant
// to be used as the first level of a two-level lookup: successful
// lookups are correct, but unsuccessful lookups need to try
// registered_types_cpp and then backfill this map if they find
// anything.
fast_type_map<type_info *> registered_types_cpp_fast;
#endif

// std::type_index -> pybind11's type information
type_map<type_info *> registered_types_cpp;
// PyTypeObject* -> base type_info(s)
Expand All @@ -261,7 +275,9 @@ struct internals {
PyObject *instance_base = nullptr;
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
thread_specific_storage<PyThreadState> tstate;
#if PYBIND11_INTERNALS_VERSION <= 11
thread_specific_storage<loader_life_support> loader_life_support_tls; // OBSOLETE (PR #5830)
#endif
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
PyInterpreterState *istate = nullptr;

Expand Down Expand Up @@ -302,7 +318,11 @@ struct internals {
// impact any other modules, because the only things accessing the local internals is the
// module that contains them.
struct local_internals {
type_map<type_info *> registered_types_cpp;
// It should be safe to use fast_type_map here because this entire
// data structure is scoped to our single module, and thus a single
// DSO and single instance of type_info for any particular type.
fast_type_map<type_info *> registered_types_cpp;

std::forward_list<ExceptionTranslator> registered_exception_translators;
PyTypeObject *function_record_py_type = nullptr;
};
Expand Down
34 changes: 28 additions & 6 deletions include/pybind11/detail/type_caster_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,21 +205,43 @@ PYBIND11_NOINLINE detail::type_info *get_type_info(PyTypeObject *type) {
return bases.front();
}

inline detail::type_info *get_local_type_info(const std::type_index &tp) {
auto &locals = get_local_internals().registered_types_cpp;
auto it = locals.find(tp);
inline detail::type_info *get_local_type_info(const std::type_info &tp) {
const auto &locals = get_local_internals().registered_types_cpp;
auto it = locals.find(&tp);
if (it != locals.end()) {
return it->second;
}
return nullptr;
}

inline detail::type_info *get_global_type_info(const std::type_index &tp) {
inline detail::type_info *get_global_type_info(const std::type_info &tp) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a comment — somewhere between terse and concise — to explain that this function implements a two-level lookup? Maybe end with "For more details see PR #5842."

(I'd add the comment inside the function, at the top.)

Ideally make it play nicely with the comment you're adding in internals.h:

#if PYBIND11_INTERNALS_VERSION >= 12
    // non-normative but fast "hint" for
    // registered_types_cpp. Successful lookups are correct;
    // unsuccessful lookups need to try registered_types_cpp and then
    // backfill this map if they find anything.
    fast_type_map<type_info *> registered_types_cpp_fast;
#endif

Mentioning "two-level lookup" there, too, would be nice.

// This is a two-level lookup. Hopefully we find the type info in
// registered_types_cpp_fast, but if not we try
// registered_types_cpp and fill registered_types_cpp_fast for
// next time.
return with_internals([&](internals &internals) {
detail::type_info *type_info = nullptr;
#if PYBIND11_INTERNALS_VERSION >= 12
auto &fast_types = internals.registered_types_cpp_fast;
#endif
auto &types = internals.registered_types_cpp;
auto it = types.find(tp);
#if PYBIND11_INTERNALS_VERSION >= 12
auto fast_it = fast_types.find(&tp);
if (fast_it != fast_types.end()) {
# ifndef NDEBUG
auto types_it = types.find(std::type_index(tp));
assert(types_it != types.end());
assert(types_it->second == fast_it->second);
# endif
return fast_it->second;
}
#endif // PYBIND11_INTERNALS_VERSION >= 12

auto it = types.find(std::type_index(tp));
if (it != types.end()) {
#if PYBIND11_INTERNALS_VERSION >= 12
fast_types.emplace(&tp, it->second);
#endif
type_info = it->second;
}
return type_info;
Expand All @@ -228,7 +250,7 @@ inline detail::type_info *get_global_type_info(const std::type_index &tp) {

/// Return the type info for a given C++ type; on lookup failure can either throw or return
/// nullptr.
PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_index &tp,
PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_info &tp,
bool throw_if_missing = false) {
if (auto *ltype = get_local_type_info(tp)) {
return ltype;
Expand Down
22 changes: 17 additions & 5 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -1637,10 +1637,14 @@ class generic_type : public object {
with_internals([&](internals &internals) {
auto tindex = std::type_index(*rec.type);
tinfo->direct_conversions = &internals.direct_conversions[tindex];
auto &local_internals = get_local_internals();
if (rec.module_local) {
get_local_internals().registered_types_cpp[tindex] = tinfo;
local_internals.registered_types_cpp[rec.type] = tinfo;
} else {
internals.registered_types_cpp[tindex] = tinfo;
#if PYBIND11_INTERNALS_VERSION >= 12
internals.registered_types_cpp_fast[rec.type] = tinfo;
#endif
}

PYBIND11_WARNING_PUSH
Expand Down Expand Up @@ -2138,10 +2142,18 @@ class class_ : public detail::generic_type {

if (has_alias) {
with_internals([&](internals &internals) {
auto &instances = record.module_local ? get_local_internals().registered_types_cpp
: internals.registered_types_cpp;
instances[std::type_index(typeid(type_alias))]
= instances[std::type_index(typeid(type))];
auto &local_internals = get_local_internals();
if (record.module_local) {
local_internals.registered_types_cpp[&typeid(type_alias)]
= local_internals.registered_types_cpp[&typeid(type)];
} else {
type_info *const val
= internals.registered_types_cpp[std::type_index(typeid(type))];
internals.registered_types_cpp[std::type_index(typeid(type_alias))] = val;
#if PYBIND11_INTERNALS_VERSION >= 12
internals.registered_types_cpp_fast[&typeid(type_alias)] = val;
#endif
}
});
}
def("_pybind11_conduit_v1_", cpp_conduit_method);
Expand Down
17 changes: 16 additions & 1 deletion tests/test_with_catch/external_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,26 @@ namespace py = pybind11;
* modules aren't preserved over a finalize/initialize.
*/

namespace {
// Compare unsafe_reset_internals_for_single_interpreter in
// test_subinterpreter.cpp.
void unsafe_reset_local_internals() {
// NOTE: This code is NOT SAFE unless the caller guarantees no other threads are alive
// NOTE: This code is tied to the precise implementation of the internals holder

py::detail::get_local_internals_pp_manager().unref();
py::detail::get_local_internals();
}
} // namespace

PYBIND11_MODULE(external_module,
m,
py::mod_gil_not_used(),
py::multiple_interpreters::per_interpreter_gil()) {

// At least one test ("Single Subinterpreter") wants to reset
// internals. We have separate local internals because we are a
// separate DSO, so ours need to be reset too!
unsafe_reset_local_internals();
class A {
public:
explicit A(int value) : v{value} {};
Expand Down
Loading