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
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 35b0035..906aac2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -192,10 +192,12 @@
     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 0000000..7e4d6fa
--- /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 <wenzel.jakob@epfl.ch>
+
+    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 efceeec..e41f882 100644
--- a/include/pybind11/detail/common.h
+++ b/include/pybind11/detail/common.h
@@ -1161,14 +1161,8 @@
                                                            /// 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 <typename T, typename SFINAE = void>
 struct format_descriptor {};
@@ -1429,3 +1423,7 @@
 
 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 0000000..de14461
--- /dev/null
+++ b/include/pybind11/detail/exception_translation-inl.h
@@ -0,0 +1,73 @@
+/*
+    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<ExceptionTranslator> &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<ExceptionTranslator> &exception_translators,
+            std::forward_list<ExceptionTranslator> &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 22ae8a1..ed18a7a 100644
--- a/include/pybind11/detail/exception_translation.h
+++ b/include/pybind11/detail/exception_translation.h
@@ -19,53 +19,13 @@
 // 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<ExceptionTranslator> &translators) {
-    auto last_exception = std::current_exception();
+bool apply_exception_translators(std::forward_list<ExceptionTranslator> &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<ExceptionTranslator> &exception_translators,
-            std::forward_list<ExceptionTranslator> &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 ce2c803..ea19984 100644
--- a/include/pybind11/detail/internals-inl.h
+++ b/include/pybind11/detail/internals-inl.h
@@ -19,6 +19,237 @@
 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<object>(PyEval_GetBuiltins());
+#else
+    auto *istate = get_interpreter_state_unchecked();
+    if (istate) {
+        state_dict = reinterpret_borrow<object>(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<const std::nested_exception *>(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;
+    }
+}
+
+// Only declared (and used) on non-libstdc++ platforms; see the comment on the
+// declaration in internals.h. Match the guard so precompiled builds do not
+// emit undeclared external-linkage definitions.
+#if !defined(__GLIBCXX__)
+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);
+    }
+}
+#endif
+
+PYBIND11_INLINE internals_pp_manager<internals> &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<internals>::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<dict>(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<uintptr_t>(&key));
+    return key;
+}
+
+PYBIND11_INLINE PyObject *get_local_internals_capsule() {
+    const auto &key = get_local_internals_key();
+    auto state_dict = reinterpret_borrow<dict>(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<local_internals> &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<local_internals>::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 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<pymutex> 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 c4c780d..b304274 100644
--- a/include/pybind11/detail/internals.h
+++ b/include/pybind11/detail/internals.h
@@ -199,7 +199,7 @@
 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)
@@ -216,22 +216,7 @@
     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<object>(PyEval_GetBuiltins());
-#else
-    auto *istate = get_interpreter_state_unchecked();
-    if (istate) {
-        state_dict = reinterpret_borrow<object>(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
@@ -339,19 +324,7 @@
 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;
@@ -530,10 +503,7 @@
 /// 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 <class T,
           enable_if_t<std::is_same<std::nested_exception, remove_cvref_t<T>>::value, int> = 0>
@@ -555,88 +525,12 @@
     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<const std::nested_exception *>(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`.
@@ -897,109 +791,34 @@
 // 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<internals> &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<internals>::get_instance(PYBIND11_INTERNALS_ID, ON_FETCH_FN);
-#undef ON_FETCH_FN
-}
+internals_pp_manager<internals> &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<dict>(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<uintptr_t>(&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<dict>(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<local_internals> &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<local_internals>::get_instance(get_local_internals_key().c_str(),
-                                                               nullptr);
-}
+internals_pp_manager<local_internals> &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)
@@ -1038,6 +857,8 @@
               local_internals.registered_exception_translators);
 }
 
+// Stays inline even in precompiled mode: it is called on every instance-map access in
+// free-threaded builds, and an out-of-line call would block inlining and constant propagation.
 inline std::uint64_t mix64(std::uint64_t z) {
     // David Stafford's variant 13 of the MurmurHash3 finalizer popularized
     // by the SplitMix PRNG.
@@ -1073,20 +894,7 @@
 
 // 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<pymutex> 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 0000000..e5c96b1
--- /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 <pybind11/detail/common-inl.h>
+#include <pybind11/pybind11.h>
diff --git a/src/exception_translation.cpp b/src/exception_translation.cpp
new file mode 100644
index 0000000..9de21b9
--- /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 <pybind11/detail/exception_translation-inl.h>
+#include <pybind11/pybind11.h>
diff --git a/src/pybind11_combined.cpp b/src/pybind11_combined.cpp
index 718ddc8..cc272ab 100644
--- a/src/pybind11_combined.cpp
+++ b/src/pybind11_combined.cpp
@@ -12,6 +12,8 @@
 #endif
 
 #include <pybind11/detail/class-inl.h>
+#include <pybind11/detail/common-inl.h>
+#include <pybind11/detail/exception_translation-inl.h>
 #include <pybind11/detail/internals-inl.h>
 #include <pybind11/detail/type_caster_base-inl.h>
 #include <pybind11/pybind11.h>
diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py
index 79af6f6..b43a5cb 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",