From 56aaa5286428740adaa7c65683a6c89ea8376235 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 23:11:27 -0400 Subject: [PATCH 1/3] feat: move internals.h, exception_translation.h, and pybind11_fail out of line The internals accessor family (get_internals, ensure_internals, the local-internals key and capsules, exception translators) moves into internals-inl.h; per-module identity is unchanged because the function-local statics move with their functions into whatever binary each module links. Also adds common-inl.h (pybind11_fail) and exception_translation-inl.h. Tiny hot accessors and all templates stay in the headers. Assisted-by: ClaudeCode:claude-fable-5 --- CMakeLists.txt | 2 + include/pybind11/detail/common-inl.h | 31 +++ include/pybind11/detail/common.h | 14 +- .../detail/exception_translation-inl.h | 71 ++++++ .../pybind11/detail/exception_translation.h | 52 +--- include/pybind11/detail/internals-inl.h | 235 +++++++++++++++++ include/pybind11/detail/internals.h | 237 ++---------------- src/common.cpp | 10 + src/exception_translation.cpp | 10 + src/pybind11_combined.cpp | 2 + tests/extra_python_package/test_files.py | 4 + 11 files changed, 395 insertions(+), 273 deletions(-) create mode 100644 include/pybind11/detail/common-inl.h create mode 100644 include/pybind11/detail/exception_translation-inl.h create mode 100644 src/common.cpp create mode 100644 src/exception_translation.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 35b0035267..906aac2c39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,10 +192,12 @@ set(PYBIND11_HEADERS include/pybind11/detail/argument_vector.h include/pybind11/detail/class-inl.h include/pybind11/detail/class.h + include/pybind11/detail/common-inl.h include/pybind11/detail/common.h include/pybind11/detail/cpp_conduit.h include/pybind11/detail/descr.h include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h + include/pybind11/detail/exception_translation-inl.h include/pybind11/detail/exception_translation.h include/pybind11/detail/function_record_pyobject.h include/pybind11/detail/function_ref.h diff --git a/include/pybind11/detail/common-inl.h b/include/pybind11/detail/common-inl.h new file mode 100644 index 0000000000..7e4d6fa7e8 --- /dev/null +++ b/include/pybind11/detail/common-inl.h @@ -0,0 +1,31 @@ +/* + pybind11/detail/common-inl.h: Out-of-line definitions for common.h + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +// Every function defined here must start with PYBIND11_INLINE (or +// PYBIND11_NOINLINE_ATTR PYBIND11_INLINE). In the default header-only mode this file is +// included at the bottom of common.h; when PYBIND11_PRECOMPILED is defined it is only +// compiled into the pybind11 static library (see src/). + +#pragma once + +#include "common.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +[[noreturn]] PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void pybind11_fail(const char *reason) { + assert(!PyErr_Occurred()); + throw std::runtime_error(reason); +} + +[[noreturn]] PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void pybind11_fail(const std::string &reason) { + assert(!PyErr_Occurred()); + throw std::runtime_error(reason); +} + +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 00d59bd1fa..6588e41667 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -1160,14 +1160,8 @@ PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybin /// casting error PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally -[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const char *reason) { - assert(!PyErr_Occurred()); - throw std::runtime_error(reason); -} -[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const std::string &reason) { - assert(!PyErr_Occurred()); - throw std::runtime_error(reason); -} +[[noreturn]] void pybind11_fail(const char *reason); +[[noreturn]] void pybind11_fail(const std::string &reason); template struct format_descriptor {}; @@ -1428,3 +1422,7 @@ inline void silence_unused_warnings(Args &&...) {} PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_PRECOMPILED +# include "common-inl.h" // IWYU pragma: export +#endif diff --git a/include/pybind11/detail/exception_translation-inl.h b/include/pybind11/detail/exception_translation-inl.h new file mode 100644 index 0000000000..50cf90f362 --- /dev/null +++ b/include/pybind11/detail/exception_translation-inl.h @@ -0,0 +1,71 @@ +/* + pybind11/detail/exception_translation-inl.h: Out-of-line definitions for exception_translation.h + + 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. +*/ + +// Every function defined here must start with PYBIND11_INLINE (or +// PYBIND11_NOINLINE_ATTR PYBIND11_INLINE). In the default header-only mode this file is +// included at the bottom of exception_translation.h; when PYBIND11_PRECOMPILED is defined +// it is only compiled into the pybind11 static library (see src/). + +#pragma once + +#include "exception_translation.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +PYBIND11_INLINE bool apply_exception_translators(std::forward_list &translators) { + auto last_exception = std::current_exception(); + + for (auto &translator : translators) { + try { + translator(last_exception); + return true; + } catch (...) { + last_exception = std::current_exception(); + } + } + return false; +} + +PYBIND11_INLINE void try_translate_exceptions() { + /* When an exception is caught, give each registered exception + translator a chance to translate it to a Python exception. First + all module-local translators will be tried in reverse order of + registration. If none of the module-locale translators handle + the exception (or there are no module-locale translators) then + the global translators will be tried, also in reverse order of + registration. + + A translator may choose to do one of the following: + + - catch the exception and call py::set_error() + to set a standard (or custom) Python exception, or + - do nothing and let the exception fall through to the next translator, or + - delegate translation to the next translator by throwing a new type of exception. + */ + + bool handled = with_exception_translators( + [&](std::forward_list &exception_translators, + std::forward_list &local_exception_translators) { + if (detail::apply_exception_translators(local_exception_translators)) { + return true; + } + if (detail::apply_exception_translators(exception_translators)) { + return true; + } + return false; + }); + + if (!handled) { + set_error(PyExc_SystemError, "Exception escaped from default exception translator!"); + } +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/exception_translation.h b/include/pybind11/detail/exception_translation.h index 22ae8a1c94..ed18a7a80b 100644 --- a/include/pybind11/detail/exception_translation.h +++ b/include/pybind11/detail/exception_translation.h @@ -19,53 +19,13 @@ PYBIND11_NAMESPACE_BEGIN(detail) // Return true if one of the translators completed without raising an exception // itself. Return of false indicates that if there are other translators // available, they should be tried. -inline bool apply_exception_translators(std::forward_list &translators) { - auto last_exception = std::current_exception(); +bool apply_exception_translators(std::forward_list &translators); - for (auto &translator : translators) { - try { - translator(last_exception); - return true; - } catch (...) { - last_exception = std::current_exception(); - } - } - return false; -} - -inline void try_translate_exceptions() { - /* When an exception is caught, give each registered exception - translator a chance to translate it to a Python exception. First - all module-local translators will be tried in reverse order of - registration. If none of the module-locale translators handle - the exception (or there are no module-locale translators) then - the global translators will be tried, also in reverse order of - registration. - - A translator may choose to do one of the following: - - - catch the exception and call py::set_error() - to set a standard (or custom) Python exception, or - - do nothing and let the exception fall through to the next translator, or - - delegate translation to the next translator by throwing a new type of exception. - */ - - bool handled = with_exception_translators( - [&](std::forward_list &exception_translators, - std::forward_list &local_exception_translators) { - if (detail::apply_exception_translators(local_exception_translators)) { - return true; - } - if (detail::apply_exception_translators(exception_translators)) { - return true; - } - return false; - }); - - if (!handled) { - set_error(PyExc_SystemError, "Exception escaped from default exception translator!"); - } -} +void try_translate_exceptions(); PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_PRECOMPILED +# include "exception_translation-inl.h" // IWYU pragma: export +#endif diff --git a/include/pybind11/detail/internals-inl.h b/include/pybind11/detail/internals-inl.h index ce2c80321d..737fd06cd4 100644 --- a/include/pybind11/detail/internals-inl.h +++ b/include/pybind11/detail/internals-inl.h @@ -19,6 +19,241 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_INLINE object get_python_state_dict() { + object state_dict; +#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) + state_dict = reinterpret_borrow(PyEval_GetBuiltins()); +#else + auto *istate = get_interpreter_state_unchecked(); + if (istate) { + state_dict = reinterpret_borrow(PyInterpreterState_GetDict(istate)); + } +#endif + if (!state_dict) { + raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED"); + throw error_already_set(); + } + return state_dict; +} + +PYBIND11_INLINE uint64_t round_up_to_next_pow2(uint64_t x) { + // Round-up to the next power of two. + // See https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + x--; + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + x |= (x >> 32); + x++; + return x; +} + +PYBIND11_INLINE std::atomic_bool &has_seen_non_main_interpreter() { + static std::atomic_bool multi(false); + return multi; +} + +PYBIND11_INLINE bool raise_err(PyObject *exc_type, const char *msg) { + if (PyErr_Occurred()) { + raise_from(exc_type, msg); + return true; + } + set_error(exc_type, msg); + return false; +} + +PYBIND11_INLINE void translate_exception(std::exception_ptr p) { + if (!p) { + return; + } + try { + std::rethrow_exception(p); + } catch (error_already_set &e) { + handle_nested_exception(e, p); + e.restore(); + return; + } catch (const builtin_exception &e) { + // Could not use template since it's an abstract class. + if (const auto *nep = dynamic_cast(std::addressof(e))) { + handle_nested_exception(*nep, p); + } + e.set_error(); + return; + } catch (const std::bad_alloc &e) { + handle_nested_exception(e, p); + raise_err(PyExc_MemoryError, e.what()); + return; + } catch (const std::domain_error &e) { + handle_nested_exception(e, p); + raise_err(PyExc_ValueError, e.what()); + return; + } catch (const std::invalid_argument &e) { + handle_nested_exception(e, p); + raise_err(PyExc_ValueError, e.what()); + return; + } catch (const std::length_error &e) { + handle_nested_exception(e, p); + raise_err(PyExc_ValueError, e.what()); + return; + } catch (const std::out_of_range &e) { + handle_nested_exception(e, p); + raise_err(PyExc_IndexError, e.what()); + return; + } catch (const std::range_error &e) { + handle_nested_exception(e, p); + raise_err(PyExc_ValueError, e.what()); + return; + } catch (const std::overflow_error &e) { + handle_nested_exception(e, p); + raise_err(PyExc_OverflowError, e.what()); + return; + } catch (const std::exception &e) { + handle_nested_exception(e, p); + raise_err(PyExc_RuntimeError, e.what()); + return; + } catch (const std::nested_exception &e) { + handle_nested_exception(e, p); + raise_err(PyExc_RuntimeError, "Caught an unknown nested exception!"); + return; + } catch (...) { + raise_err(PyExc_RuntimeError, "Caught an unknown exception!"); + return; + } +} + +PYBIND11_INLINE void translate_local_exception(std::exception_ptr p) { + try { + if (p) { + std::rethrow_exception(p); + } + } catch (error_already_set &e) { + e.restore(); + return; + } catch (const builtin_exception &e) { + e.set_error(); + return; + } +} + +PYBIND11_INLINE void check_internals_local_exception_translator(internals *internals_ptr) { + if (internals_ptr) { + for (auto et : internals_ptr->registered_exception_translators) { + if (et == &translate_local_exception) { + return; + } + } + internals_ptr->registered_exception_translators.push_front(&translate_local_exception); + } +} + +PYBIND11_INLINE internals_pp_manager &get_internals_pp_manager() { +#if defined(__GLIBCXX__) +# define ON_FETCH_FN nullptr +#else +# define ON_FETCH_FN &check_internals_local_exception_translator +#endif + return internals_pp_manager::get_instance(PYBIND11_INTERNALS_ID, ON_FETCH_FN); +#undef ON_FETCH_FN +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE internals &get_internals() { + auto &ppmgr = get_internals_pp_manager(); + auto *pp = ppmgr.get_pp(); + if (!pp) { + pybind11_fail("get_internals: get_pp() returned nullptr"); + } + auto &internals_ptr = *pp; + if (!internals_ptr) { + // Slow path, something needs fetched from the state dict or created + gil_scoped_acquire_simple gil; + error_scope err_scope; + + ppmgr.create_pp_content_once(&internals_ptr); + + if (!internals_ptr) { + pybind11_fail("get_internals: create_pp_content_once() produced nullptr"); + } + if (!internals_ptr->instance_base) { + // This calls get_internals, so cannot be called from within the internals constructor + // called above because internals_ptr must be set before get_internals is called again + internals_ptr->instance_base = make_object_base_type(internals_ptr->default_metaclass); + } + } + return *internals_ptr; +} + +PYBIND11_INLINE PyObject *get_internals_capsule() { + auto state_dict = reinterpret_borrow(get_python_state_dict()); + return dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID); +} + +PYBIND11_INLINE const std::string &get_local_internals_key() { + static const std::string key + = PYBIND11_MODULE_LOCAL_ID + std::to_string(reinterpret_cast(&key)); + return key; +} + +PYBIND11_INLINE PyObject *get_local_internals_capsule() { + const auto &key = get_local_internals_key(); + auto state_dict = reinterpret_borrow(get_python_state_dict()); + return dict_getitemstring(state_dict.ptr(), key.c_str()); +} + +PYBIND11_INLINE void ensure_internals() { + pybind11::detail::get_internals_pp_manager().unref(); +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT + if (PyInterpreterState_Get() != PyInterpreterState_Main()) { + has_seen_non_main_interpreter() = true; + } +#endif + pybind11::detail::get_internals(); +} + +PYBIND11_INLINE internals_pp_manager &get_local_internals_pp_manager() { + // Use the address of a static variable as part of the key, so that the value is uniquely tied + // to where the module is loaded in memory + return internals_pp_manager::get_instance(get_local_internals_key().c_str(), + nullptr); +} + +PYBIND11_INLINE local_internals &get_local_internals() { + auto &ppmgr = get_local_internals_pp_manager(); + auto &internals_ptr = *ppmgr.get_pp(); + if (!internals_ptr) { + gil_scoped_acquire_simple gil; + error_scope err_scope; + + ppmgr.create_pp_content_once(&internals_ptr); + } + return *internals_ptr; +} + +PYBIND11_INLINE std::uint64_t mix64(std::uint64_t z) { + // David Stafford's variant 13 of the MurmurHash3 finalizer popularized + // by the SplitMix PRNG. + // https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); +} + +PYBIND11_INLINE size_t num_registered_instances() { + auto &internals = get_internals(); +#ifdef Py_GIL_DISABLED + size_t count = 0; + for (size_t i = 0; i <= internals.instance_shards_mask; ++i) { + auto &shard = internals.instance_shards[i]; + std::unique_lock lock(shard.mutex); + count += shard.registered_instances.size(); + } + return count; +#else + return internals.registered_instances.size(); +#endif +} + #if defined(PYBIND11_PRECOMPILED) // Link-time configuration guard; see the declaration in internals.h. PYBIND11_INLINE void PYBIND11_PRECOMPILED_CONFIG_CHECK() {} diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index fede8aef37..7a4d75a9cd 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -182,7 +182,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) PyTypeObject *make_static_property_type(); PyTypeObject *make_default_metaclass(); PyObject *make_object_base_type(PyTypeObject *metaclass); -inline void translate_exception(std::exception_ptr p); +void translate_exception(std::exception_ptr p); inline PyThreadState *get_thread_state_unchecked() { #if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) @@ -199,22 +199,7 @@ inline PyInterpreterState *get_interpreter_state_unchecked() { return tstate ? tstate->interp : nullptr; } -inline object get_python_state_dict() { - object state_dict; -#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) - state_dict = reinterpret_borrow(PyEval_GetBuiltins()); -#else - auto *istate = get_interpreter_state_unchecked(); - if (istate) { - state_dict = reinterpret_borrow(PyInterpreterState_GetDict(istate)); - } -#endif - if (!state_dict) { - raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED"); - throw error_already_set(); - } - return state_dict; -} +object get_python_state_dict(); // Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly // other STLs, this means `typeid(A)` from one module won't equal `typeid(A)` from another module @@ -322,19 +307,7 @@ struct instance_map_shard { static_assert(sizeof(instance_map_shard) % 64 == 0, "instance_map_shard size is not a multiple of 64 bytes"); -inline uint64_t round_up_to_next_pow2(uint64_t x) { - // Round-up to the next power of two. - // See https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - x--; - x |= (x >> 1); - x |= (x >> 2); - x |= (x >> 4); - x |= (x >> 8); - x |= (x >> 16); - x |= (x >> 32); - x++; - return x; -} +uint64_t round_up_to_next_pow2(uint64_t x); #endif class loader_life_support; @@ -513,10 +486,7 @@ struct native_enum_record { /// We use this to figure out if there are or have been multiple subinterpreters active at any /// point. This must never go from true to false while any interpreter may be running in any /// thread! -inline std::atomic_bool &has_seen_non_main_interpreter() { - static std::atomic_bool multi(false); - return multi; -} +std::atomic_bool &has_seen_non_main_interpreter(); template >::value, int> = 0> @@ -538,88 +508,12 @@ bool handle_nested_exception(const T &exc, const std::exception_ptr &p) { return false; } -inline bool raise_err(PyObject *exc_type, const char *msg) { - if (PyErr_Occurred()) { - raise_from(exc_type, msg); - return true; - } - set_error(exc_type, msg); - return false; -} +bool raise_err(PyObject *exc_type, const char *msg); -inline void translate_exception(std::exception_ptr p) { - if (!p) { - return; - } - try { - std::rethrow_exception(p); - } catch (error_already_set &e) { - handle_nested_exception(e, p); - e.restore(); - return; - } catch (const builtin_exception &e) { - // Could not use template since it's an abstract class. - if (const auto *nep = dynamic_cast(std::addressof(e))) { - handle_nested_exception(*nep, p); - } - e.set_error(); - return; - } catch (const std::bad_alloc &e) { - handle_nested_exception(e, p); - raise_err(PyExc_MemoryError, e.what()); - return; - } catch (const std::domain_error &e) { - handle_nested_exception(e, p); - raise_err(PyExc_ValueError, e.what()); - return; - } catch (const std::invalid_argument &e) { - handle_nested_exception(e, p); - raise_err(PyExc_ValueError, e.what()); - return; - } catch (const std::length_error &e) { - handle_nested_exception(e, p); - raise_err(PyExc_ValueError, e.what()); - return; - } catch (const std::out_of_range &e) { - handle_nested_exception(e, p); - raise_err(PyExc_IndexError, e.what()); - return; - } catch (const std::range_error &e) { - handle_nested_exception(e, p); - raise_err(PyExc_ValueError, e.what()); - return; - } catch (const std::overflow_error &e) { - handle_nested_exception(e, p); - raise_err(PyExc_OverflowError, e.what()); - return; - } catch (const std::exception &e) { - handle_nested_exception(e, p); - raise_err(PyExc_RuntimeError, e.what()); - return; - } catch (const std::nested_exception &e) { - handle_nested_exception(e, p); - raise_err(PyExc_RuntimeError, "Caught an unknown nested exception!"); - return; - } catch (...) { - raise_err(PyExc_RuntimeError, "Caught an unknown exception!"); - return; - } -} +void translate_exception(std::exception_ptr p); #if !defined(__GLIBCXX__) -inline void translate_local_exception(std::exception_ptr p) { - try { - if (p) { - std::rethrow_exception(p); - } - } catch (error_already_set &e) { - e.restore(); - return; - } catch (const builtin_exception &e) { - e.set_error(); - return; - } -} +void translate_local_exception(std::exception_ptr p); #endif // Sentinel value for the `dtor` parameter of `atomic_get_or_create_in_state_dict`. @@ -880,109 +774,34 @@ class internals_pp_manager { // libc++ with CPython doesn't require this (types are explicitly exported) // libc++ with PyPy still need it, awaiting further investigation #if !defined(__GLIBCXX__) -inline void check_internals_local_exception_translator(internals *internals_ptr) { - if (internals_ptr) { - for (auto et : internals_ptr->registered_exception_translators) { - if (et == &translate_local_exception) { - return; - } - } - internals_ptr->registered_exception_translators.push_front(&translate_local_exception); - } -} +void check_internals_local_exception_translator(internals *internals_ptr); #endif -inline internals_pp_manager &get_internals_pp_manager() { -#if defined(__GLIBCXX__) -# define ON_FETCH_FN nullptr -#else -# define ON_FETCH_FN &check_internals_local_exception_translator -#endif - return internals_pp_manager::get_instance(PYBIND11_INTERNALS_ID, ON_FETCH_FN); -#undef ON_FETCH_FN -} +internals_pp_manager &get_internals_pp_manager(); /// Return a reference to the current `internals` data -PYBIND11_NOINLINE internals &get_internals() { - auto &ppmgr = get_internals_pp_manager(); - auto *pp = ppmgr.get_pp(); - if (!pp) { - pybind11_fail("get_internals: get_pp() returned nullptr"); - } - auto &internals_ptr = *pp; - if (!internals_ptr) { - // Slow path, something needs fetched from the state dict or created - gil_scoped_acquire_simple gil; - error_scope err_scope; - - ppmgr.create_pp_content_once(&internals_ptr); - - if (!internals_ptr) { - pybind11_fail("get_internals: create_pp_content_once() produced nullptr"); - } - if (!internals_ptr->instance_base) { - // This calls get_internals, so cannot be called from within the internals constructor - // called above because internals_ptr must be set before get_internals is called again - internals_ptr->instance_base = make_object_base_type(internals_ptr->default_metaclass); - } - } - return *internals_ptr; -} +internals &get_internals(); /// Return the PyObject* for the internals capsule (borrowed reference). /// Returns nullptr if the capsule doesn't exist yet. -inline PyObject *get_internals_capsule() { - auto state_dict = reinterpret_borrow(get_python_state_dict()); - return dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID); -} +PyObject *get_internals_capsule(); /// Return the key used for local_internals in the state dict. /// This function ensures a consistent key is used across all call sites within the same /// compilation unit. The key includes the address of a static variable to make it unique per /// module (DSO), matching the behavior of get_local_internals_pp_manager(). -inline const std::string &get_local_internals_key() { - static const std::string key - = PYBIND11_MODULE_LOCAL_ID + std::to_string(reinterpret_cast(&key)); - return key; -} +const std::string &get_local_internals_key(); /// Return the PyObject* for the local_internals capsule (borrowed reference). /// Returns nullptr if the capsule doesn't exist yet. -inline PyObject *get_local_internals_capsule() { - const auto &key = get_local_internals_key(); - auto state_dict = reinterpret_borrow(get_python_state_dict()); - return dict_getitemstring(state_dict.ptr(), key.c_str()); -} +PyObject *get_local_internals_capsule(); -inline void ensure_internals() { - pybind11::detail::get_internals_pp_manager().unref(); -#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT - if (PyInterpreterState_Get() != PyInterpreterState_Main()) { - has_seen_non_main_interpreter() = true; - } -#endif - pybind11::detail::get_internals(); -} +void ensure_internals(); -inline internals_pp_manager &get_local_internals_pp_manager() { - // Use the address of a static variable as part of the key, so that the value is uniquely tied - // to where the module is loaded in memory - return internals_pp_manager::get_instance(get_local_internals_key().c_str(), - nullptr); -} +internals_pp_manager &get_local_internals_pp_manager(); /// Works like `get_internals`, but for things which are locally registered. -inline local_internals &get_local_internals() { - auto &ppmgr = get_local_internals_pp_manager(); - auto &internals_ptr = *ppmgr.get_pp(); - if (!internals_ptr) { - gil_scoped_acquire_simple gil; - error_scope err_scope; - - ppmgr.create_pp_content_once(&internals_ptr); - } - return *internals_ptr; -} +local_internals &get_local_internals(); #ifdef Py_GIL_DISABLED # define PYBIND11_LOCK_INTERNALS(internals) pycritical_section lock((internals).mutex) @@ -1021,14 +840,7 @@ inline auto with_exception_translators(const F &cb) local_internals.registered_exception_translators); } -inline std::uint64_t mix64(std::uint64_t z) { - // David Stafford's variant 13 of the MurmurHash3 finalizer popularized - // by the SplitMix PRNG. - // https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); -} +std::uint64_t mix64(std::uint64_t z); template inline auto with_instance_map(const void *ptr, const F &cb) @@ -1056,20 +868,7 @@ inline auto with_instance_map(const void *ptr, const F &cb) // Returns the number of registered instances for testing purposes. The result may not be // consistent if other threads are registering or unregistering instances concurrently. -inline size_t num_registered_instances() { - auto &internals = get_internals(); -#ifdef Py_GIL_DISABLED - size_t count = 0; - for (size_t i = 0; i <= internals.instance_shards_mask; ++i) { - auto &shard = internals.instance_shards[i]; - std::unique_lock lock(shard.mutex); - count += shard.registered_instances.size(); - } - return count; -#else - return internals.registered_instances.size(); -#endif -} +size_t num_registered_instances(); /// Constructs a std::string with the given arguments, stores it in `internals`, and returns its /// `c_str()`. Such strings objects have a long storage duration -- the internal strings are only diff --git a/src/common.cpp b/src/common.cpp new file mode 100644 index 0000000000..e5c96b162a --- /dev/null +++ b/src/common.cpp @@ -0,0 +1,10 @@ +// Copyright (c) 2025 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. + +#if !defined(PYBIND11_PRECOMPILED) +# error "pybind11 library sources must be compiled with PYBIND11_PRECOMPILED defined." +#endif + +#include +#include diff --git a/src/exception_translation.cpp b/src/exception_translation.cpp new file mode 100644 index 0000000000..9de21b90b0 --- /dev/null +++ b/src/exception_translation.cpp @@ -0,0 +1,10 @@ +// Copyright (c) 2025 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. + +#if !defined(PYBIND11_PRECOMPILED) +# error "pybind11 library sources must be compiled with PYBIND11_PRECOMPILED defined." +#endif + +#include +#include diff --git a/src/pybind11_combined.cpp b/src/pybind11_combined.cpp index 718ddc8291..cc272ab5f5 100644 --- a/src/pybind11_combined.cpp +++ b/src/pybind11_combined.cpp @@ -12,6 +12,8 @@ #endif #include +#include +#include #include #include #include diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 79af6f679e..b43a5cbdb9 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -84,6 +84,7 @@ "include/pybind11/detail/argument_vector.h", "include/pybind11/detail/class-inl.h", "include/pybind11/detail/class.h", + "include/pybind11/detail/common-inl.h", "include/pybind11/detail/common.h", "include/pybind11/detail/cpp_conduit.h", "include/pybind11/detail/descr.h", @@ -102,6 +103,7 @@ "include/pybind11/detail/typeid.h", "include/pybind11/detail/using_smart_holder.h", "include/pybind11/detail/value_and_holder.h", + "include/pybind11/detail/exception_translation-inl.h", "include/pybind11/detail/exception_translation.h", } @@ -132,6 +134,8 @@ sdist_src_files = { "src/class.cpp", + "src/common.cpp", + "src/exception_translation.cpp", "src/internals.cpp", "src/type_caster_base.cpp", "src/pybind11_combined.cpp", From 657dd9310582b2d0d1db52adaf27d4a8a7e17979 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 03:13:00 +0000 Subject: [PATCH 2/3] style: pre-commit fixes --- include/pybind11/detail/exception_translation-inl.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/pybind11/detail/exception_translation-inl.h b/include/pybind11/detail/exception_translation-inl.h index 50cf90f362..de14461f2c 100644 --- a/include/pybind11/detail/exception_translation-inl.h +++ b/include/pybind11/detail/exception_translation-inl.h @@ -1,5 +1,6 @@ /* - pybind11/detail/exception_translation-inl.h: Out-of-line definitions for exception_translation.h + pybind11/detail/exception_translation-inl.h: Out-of-line definitions for + exception_translation.h Copyright (c) 2024 The Pybind Development Team. @@ -19,7 +20,8 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) -PYBIND11_INLINE bool apply_exception_translators(std::forward_list &translators) { +PYBIND11_INLINE bool +apply_exception_translators(std::forward_list &translators) { auto last_exception = std::current_exception(); for (auto &translator : translators) { From c7a72cbca0a2ee1a10441c7aaa40d31843265753 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 7 Aug 2026 12:25:25 -0400 Subject: [PATCH 3/3] fix: drop duplicate translate_exception declaration Assisted-by: ClaudeCode:claude-fable-5 --- include/pybind11/detail/internals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 7a4d75a9cd..6a837dbf3f 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -510,7 +510,7 @@ bool handle_nested_exception(const T &exc, const std::exception_ptr &p) { bool raise_err(PyObject *exc_type, const char *msg); -void translate_exception(std::exception_ptr p); +// translate_exception is forward-declared near the top of this header #if !defined(__GLIBCXX__) void translate_local_exception(std::exception_ptr p);