From 723bc465a3dc049f85b03d0a1c6135a95061e6a0 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 21:28:16 -0400 Subject: [PATCH 01/14] feat: add PYBIND11_NOINLINE_ATTR and PYBIND11_INLINE macros Decompose PYBIND11_NOINLINE into the attribute part plus inline, and add PYBIND11_INLINE, which becomes empty under PYBIND11_PRECOMPILED. Groundwork for optional pre-compilation; all current expansions are unchanged and PYBIND11_INLINE is not used yet. Assisted-by: ClaudeCode:claude-fable-5 --- include/pybind11/detail/common.h | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 740001db78..506a451a0e 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -160,11 +160,22 @@ // In contrast, FORWARD DECLARATIONS should never use this macro: // https://stackoverflow.com/questions/9317473/forward-declaration-of-inline-functions #if defined(PYBIND11_NOINLINE_DISABLED) // Option for maximum portability and experimentation. -# define PYBIND11_NOINLINE inline +# define PYBIND11_NOINLINE_ATTR #elif defined(_MSC_VER) -# define PYBIND11_NOINLINE __declspec(noinline) inline +# define PYBIND11_NOINLINE_ATTR __declspec(noinline) #else -# define PYBIND11_NOINLINE __attribute__((noinline)) inline +# define PYBIND11_NOINLINE_ATTR __attribute__((noinline)) +#endif +#define PYBIND11_NOINLINE PYBIND11_NOINLINE_ATTR inline + +// PYBIND11_INLINE marks function definitions that live in a `-inl.h` file. It is `inline` in +// the default header-only mode. Defining PYBIND11_PRECOMPILED makes it empty: the definitions +// are then compiled once (into a static library linked into each extension module) and the +// headers only provide declarations. +#if defined(PYBIND11_PRECOMPILED) +# define PYBIND11_INLINE +#else +# define PYBIND11_INLINE inline #endif #if defined(_MSC_VER) From 83141452349cd91a4a5867c913b5744a25458fba Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 21:54:53 -0400 Subject: [PATCH 02/14] feat(cmake): opt-in precompiled pybind11 static library, starting with pytypes.h Split the out-of-line pytypes.h definitions into pytypes-inl.h (fmtlib/ CLI11 style): inline by default, compiled once into a per-project static library when PYBIND11_PRECOMPILED is defined. Infrastructure: - pybind11_precompile() creates the lazy pybind11::precompiled STATIC library from the installed or in-tree src/ sources; PRECOMPILE / NO_PRECOMPILE keywords on pybind11_add_module and a global PYBIND11_PRECOMPILE switch select it per target. - A link-time guard symbol encodes PYBIND11_INTERNALS_VERSION, Py_GIL_DISABLED, PYBIND11_SIMPLE_GIL_MANAGEMENT, and PYBIND11_DETAILED_ERROR_MESSAGES, so a configuration mismatch is one readable undefined symbol. - src/ is installed to share/pybind11/src (wheel stays pure); src/pybind11_combined.cpp is a single-TU build for non-CMake use. - Tests: PYBIND11_TEST_PRECOMPILE builds the whole suite against the library, two new test_cmake_build cases, packaging file lists, tidy preset, and a 3-platform CI job. Assisted-by: ClaudeCode:claude-fable-5 --- .github/workflows/ci.yml | 25 ++ CMakeLists.txt | 14 + CMakePresets.json | 3 +- docs/Doxyfile | 1 + include/pybind11/detail/common.h | 2 + include/pybind11/detail/internals-inl.h | 28 ++ include/pybind11/detail/internals.h | 60 ++++ include/pybind11/pytypes-inl.h | 280 ++++++++++++++++++ include/pybind11/pytypes.h | 259 +--------------- src/internals.cpp | 10 + src/pybind11_combined.cpp | 16 + src/pytypes.cpp | 12 + tests/CMakeLists.txt | 18 +- tests/extra_python_package/test_files.py | 16 +- tests/test_cmake_build/CMakeLists.txt | 2 + .../installed_precompile/CMakeLists.txt | 28 ++ .../subdirectory_precompile/CMakeLists.txt | 32 ++ tools/pybind11Common.cmake | 72 +++++ tools/pybind11Config.cmake.in | 3 + tools/pybind11NewTools.cmake | 19 +- tools/pybind11Tools.cmake | 18 +- 21 files changed, 638 insertions(+), 280 deletions(-) create mode 100644 include/pybind11/detail/internals-inl.h create mode 100644 include/pybind11/pytypes-inl.h create mode 100644 src/internals.cpp create mode 100644 src/pybind11_combined.cpp create mode 100644 src/pytypes.cpp create mode 100644 tests/test_cmake_build/installed_precompile/CMakeLists.txt create mode 100644 tests/test_cmake_build/subdirectory_precompile/CMakeLists.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb2fcf2b67..a16875a0c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,31 @@ jobs: python-version: ${{ matrix.python-version }} cmake-args: ${{ matrix.cmake-args }} + # Build the test suite against the precompiled pybind11 static library; a + # definition missing from the -inl.h split shows up here as a link error. + precompile: + if: github.event.pull_request.draft == false + strategy: + fail-fast: false + matrix: + include: + - runs-on: ubuntu-latest + python-version: '3.13' + # free-threaded: compiles the Py_GIL_DISABLED-only code in the -inl.h files + - runs-on: ubuntu-latest + python-version: '3.14t' + - runs-on: macos-latest + python-version: '3.13' + - runs-on: windows-2022 + python-version: '3.13' + + name: ⚡ + uses: ./.github/workflows/reusable-standard.yml + with: + runs-on: ${{ matrix.runs-on }} + python-version: ${{ matrix.python-version }} + cmake-args: -DPYBIND11_TEST_PRECOMPILE=ON + standard-large: if: github.event.pull_request.draft == false strategy: diff --git a/CMakeLists.txt b/CMakeLists.txt index 65f794a49d..b65529c9b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,6 +200,7 @@ set(PYBIND11_HEADERS include/pybind11/detail/function_ref.h include/pybind11/detail/holder_caster_foreign_helpers.h include/pybind11/detail/init.h + include/pybind11/detail/internals-inl.h include/pybind11/detail/internals.h include/pybind11/detail/native_enum_data.h include/pybind11/detail/pybind11_namespace_macros.h @@ -234,6 +235,7 @@ set(PYBIND11_HEADERS include/pybind11/numpy.h include/pybind11/operators.h include/pybind11/pybind11.h + include/pybind11/pytypes-inl.h include/pybind11/pytypes.h include/pybind11/subinterpreter.h include/pybind11/stl.h @@ -265,6 +267,11 @@ endif() list(TRANSFORM PYBIND11_HEADERS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") +# Library sources for the opt-in precompiled mode (pybind11_precompile()). +set(pybind11_SRC_DIR + "${CMAKE_CURRENT_SOURCE_DIR}/src" + CACHE INTERNAL "Directory containing the pybind11 library sources") + # Cache variable so this can be used in parent projects set(pybind11_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include" @@ -331,6 +338,8 @@ if(PYBIND11_INSTALL) install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 DESTINATION "${SKBUILD_HEADERS_DIR}") endif() install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pybind11/src") set(PYBIND11_CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}" CACHE STRING "install path for pybind11Config.cmake") @@ -340,6 +349,11 @@ if(PYBIND11_INSTALL) else() set(pybind11_INCLUDEDIR "\$\{PACKAGE_PREFIX_DIR\}/${CMAKE_INSTALL_INCLUDEDIR}") endif() + if(IS_ABSOLUTE "${CMAKE_INSTALL_DATAROOTDIR}") + set(pybind11_SRCDIR "${CMAKE_INSTALL_DATAROOTDIR}/pybind11/src") + else() + set(pybind11_SRCDIR "\$\{PACKAGE_PREFIX_DIR\}/${CMAKE_INSTALL_DATAROOTDIR}/pybind11/src") + endif() configure_package_config_file( tools/${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" diff --git a/CMakePresets.json b/CMakePresets.json index 42bf3ade9d..6e86aa331f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -36,7 +36,8 @@ "binaryDir": "build-tidy", "cacheVariables": { "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--use-color;--warnings-as-errors=*", - "CMAKE_CXX_STANDARD": "17" + "CMAKE_CXX_STANDARD": "17", + "PYBIND11_TEST_PRECOMPILE": "ON" } } ], diff --git a/docs/Doxyfile b/docs/Doxyfile index 09138db364..d0c184ccc0 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,6 +1,7 @@ PROJECT_NAME = pybind11 INPUT = ../include/pybind11/ RECURSIVE = YES +EXCLUDE_PATTERNS = *-inl.h GENERATE_HTML = NO GENERATE_LATEX = NO diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 506a451a0e..efceeec689 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -444,6 +444,7 @@ static PyObject *pybind11_init(); \ PYBIND11_PLUGIN_IMPL(name) { \ PYBIND11_CHECK_PYTHON_VERSION \ + PYBIND11_PRECOMPILED_CONFIG_GUARD \ PYBIND11_ENSURE_INTERNALS_READY \ try { \ return pybind11_init(); \ @@ -468,6 +469,7 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \ PYBIND11_PLUGIN_IMPL(name) { \ PYBIND11_CHECK_PYTHON_VERSION \ + PYBIND11_PRECOMPILED_CONFIG_GUARD \ try { \ pybind11::detail::ensure_internals(); \ static ::pybind11::detail::slots_array mod_def_slots \ diff --git a/include/pybind11/detail/internals-inl.h b/include/pybind11/detail/internals-inl.h new file mode 100644 index 0000000000..ce2c80321d --- /dev/null +++ b/include/pybind11/detail/internals-inl.h @@ -0,0 +1,28 @@ +/* + pybind11/detail/internals-inl.h: Out-of-line definitions for internals.h + + Copyright (c) 2017 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 internals.h; when PYBIND11_PRECOMPILED is defined it is only +// compiled into the pybind11 static library (see src/). + +#pragma once + +#include "internals.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +#if defined(PYBIND11_PRECOMPILED) +// Link-time configuration guard; see the declaration in internals.h. +PYBIND11_INLINE void PYBIND11_PRECOMPILED_CONFIG_CHECK() {} +#endif + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 295485ffab..274e794b5f 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -46,8 +46,64 @@ # error "PYBIND11_INTERNALS_VERSION 12 is the minimum for all platforms for pybind11 v3.1.0" #endif +#if defined(PYBIND11_PRECOMPILED) +// PYBIND11_PRECOMPILED_CONFIG_CHECK names a do-nothing function defined in the precompiled +// pybind11 library. The identifier encodes every configuration macro that must match between +// the library and the modules linking it. PYBIND11_MODULE calls it, so a mismatch (or a +// missing library) surfaces as one readable undefined symbol at link time instead of many +// unrelated ones at run time. If you add a configuration macro that changes the code in the +// -inl.h files, encode it here and add it to the list in docs/compiling.rst. +# if defined(Py_GIL_DISABLED) +# define PYBIND11_PRECOMPILED_CFG_GD 1 +# else +# define PYBIND11_PRECOMPILED_CFG_GD 0 +# endif +# if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) +# define PYBIND11_PRECOMPILED_CFG_SG 1 +# else +# define PYBIND11_PRECOMPILED_CFG_SG 0 +# endif +# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) +# define PYBIND11_PRECOMPILED_CFG_DE 1 +# else +# define PYBIND11_PRECOMPILED_CFG_DE 0 +# endif +# if defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT) +# define PYBIND11_PRECOMPILED_CFG_SI 1 +# else +# define PYBIND11_PRECOMPILED_CFG_SI 0 +# endif +# if defined(PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET) +# define PYBIND11_PRECOMPILED_CFG_TD 1 +# else +# define PYBIND11_PRECOMPILED_CFG_TD 0 +# endif +// PYBIND11_CONCAT does not macro-expand its arguments (## suppresses expansion). +# define PYBIND11_PRECOMPILED_CONFIG_NAME_(v, gd, sg, de, si, td) \ + pybind11_precompiled_config_v##v##_gd##gd##_sg##sg##_de##de##_si##si##_td##td +# define PYBIND11_PRECOMPILED_CONFIG_NAME(v, gd, sg, de, si, td) \ + PYBIND11_PRECOMPILED_CONFIG_NAME_(v, gd, sg, de, si, td) +# define PYBIND11_PRECOMPILED_CONFIG_CHECK \ + PYBIND11_PRECOMPILED_CONFIG_NAME(PYBIND11_INTERNALS_VERSION, \ + PYBIND11_PRECOMPILED_CFG_GD, \ + PYBIND11_PRECOMPILED_CFG_SG, \ + PYBIND11_PRECOMPILED_CFG_DE, \ + PYBIND11_PRECOMPILED_CFG_SI, \ + PYBIND11_PRECOMPILED_CFG_TD) +# define PYBIND11_PRECOMPILED_CONFIG_GUARD \ + ::pybind11::detail::PYBIND11_PRECOMPILED_CONFIG_CHECK(); +#else +# define PYBIND11_PRECOMPILED_CONFIG_GUARD +#endif + PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +#if defined(PYBIND11_PRECOMPILED) +PYBIND11_NAMESPACE_BEGIN(detail) +void PYBIND11_PRECOMPILED_CONFIG_CHECK(); +PYBIND11_NAMESPACE_END(detail) +#endif + using ExceptionTranslator = void (*)(std::exception_ptr); // The old Python Thread Local Storage (TLS) API is deprecated in Python 3.7 in favor of the new @@ -1084,3 +1140,7 @@ T &get_or_create_shared_data(const std::string &name) { } PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_PRECOMPILED +# include "internals-inl.h" // IWYU pragma: export +#endif diff --git a/include/pybind11/pytypes-inl.h b/include/pybind11/pytypes-inl.h new file mode 100644 index 0000000000..f2eea1070f --- /dev/null +++ b/include/pybind11/pytypes-inl.h @@ -0,0 +1,280 @@ +/* + pybind11/pytypes-inl.h: Out-of-line definitions for pytypes.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 pytypes.h; when PYBIND11_PRECOMPILED is defined it is only +// compiled into the pybind11 static library (see src/). + +#pragma once + +#include "pytypes.h" + +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +PYBIND11_INLINE error_fetch_and_normalize::error_fetch_and_normalize(const char *called) { + PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); + if (!m_type) { + pybind11_fail("Internal error: " + std::string(called) + + " called while " + "Python error indicator not set."); + } + const char *exc_type_name_orig = detail::obj_class_name(m_type.ptr()); + if (exc_type_name_orig == nullptr) { + pybind11_fail("Internal error: " + std::string(called) + + " failed to obtain the name " + "of the original active exception type."); + } + m_lazy_error_string = exc_type_name_orig; +#if PY_VERSION_HEX >= 0x030C0000 + // The presence of __notes__ is likely due to exception normalization + // errors, although that is not necessarily true, therefore insert a + // hint only: + const int has_notes = PyObject_HasAttrString(m_value.ptr(), "__notes__"); + if (has_notes == 1) { + m_lazy_error_string += "[WITH __notes__]"; + } else if (has_notes == -1) { + // Ignore secondary errors when probing for __notes__ to avoid leaking a + // spurious exception while still reporting the original error. + PyErr_Clear(); + } +#else + // PyErr_NormalizeException() may change the exception type if there are cascading + // failures. This can potentially be extremely confusing. + PyErr_NormalizeException(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); + if (m_type.ptr() == nullptr) { + pybind11_fail("Internal error: " + std::string(called) + + " failed to normalize the " + "active exception."); + } + const char *exc_type_name_norm = detail::obj_class_name(m_type.ptr()); + if (exc_type_name_norm == nullptr) { + pybind11_fail("Internal error: " + std::string(called) + + " failed to obtain the name " + "of the normalized active exception type."); + } + if (exc_type_name_norm != m_lazy_error_string) { + std::string msg = std::string(called) + + ": MISMATCH of original and normalized " + "active exception types: "; + msg += "ORIGINAL "; + msg += m_lazy_error_string; + msg += " REPLACED BY "; + msg += exc_type_name_norm; + msg += ": " + format_value_and_trace(); + pybind11_fail(msg); + } +#endif +} + +PYBIND11_INLINE std::string error_fetch_and_normalize::format_value_and_trace() const { + std::string result; + std::string message_error_string; + if (m_value) { + auto value_str = reinterpret_steal(PyObject_Str(m_value.ptr())); + constexpr const char *message_unavailable_exc + = ""; + if (!value_str) { + message_error_string = detail::error_string(); + result = message_unavailable_exc; + } else { + // Not using `value_str.cast()`, to not potentially throw a secondary + // error_already_set that will then result in process termination (#4288). + auto value_bytes = reinterpret_steal( + PyUnicode_AsEncodedString(value_str.ptr(), "utf-8", "backslashreplace")); + if (!value_bytes) { + message_error_string = detail::error_string(); + result = message_unavailable_exc; + } else { + char *buffer = nullptr; + Py_ssize_t length = 0; + if (PyBytes_AsStringAndSize(value_bytes.ptr(), &buffer, &length) == -1) { + message_error_string = detail::error_string(); + result = message_unavailable_exc; + } else { + result = std::string(buffer, static_cast(length)); + } + } + } +#if PY_VERSION_HEX >= 0x030B0000 + auto notes = reinterpret_steal(PyObject_GetAttrString(m_value.ptr(), "__notes__")); + if (!notes) { + PyErr_Clear(); // No notes is good news. + } else { + auto len_notes = PyList_Size(notes.ptr()); + if (len_notes < 0) { + result += "\nFAILURE obtaining len(__notes__): " + detail::error_string(); + } else { + result += "\n__notes__ (len=" + std::to_string(len_notes) + "):"; + for (ssize_t i = 0; i < len_notes; i++) { + PyObject *note = PyList_GET_ITEM(notes.ptr(), i); + auto note_bytes = reinterpret_steal( + PyUnicode_AsEncodedString(note, "utf-8", "backslashreplace")); + if (!note_bytes) { + result += "\nFAILURE obtaining __notes__[" + std::to_string(i) + + "]: " + detail::error_string(); + } else { + char *buffer = nullptr; + Py_ssize_t length = 0; + if (PyBytes_AsStringAndSize(note_bytes.ptr(), &buffer, &length) == -1) { + result += "\nFAILURE formatting __notes__[" + std::to_string(i) + + "]: " + detail::error_string(); + } else { + result += '\n'; + result += std::string(buffer, static_cast(length)); + } + } + } + } + } +#endif + } else { + result = ""; + } + if (result.empty()) { + result = ""; + } + + bool have_trace = false; + if (m_trace) { +#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) + auto *tb = reinterpret_cast(m_trace.ptr()); + + // Get the deepest trace possible. + while (tb->tb_next) { + tb = tb->tb_next; + } + + PyFrameObject *frame = tb->tb_frame; + Py_XINCREF(frame); + result += "\n\nAt:\n"; + while (frame) { + PyCodeObject *f_code = PyFrame_GetCode(frame); + int lineno = PyFrame_GetLineNumber(frame); + result += " "; + result += handle(f_code->co_filename).cast(); + result += '('; + result += std::to_string(lineno); + result += "): "; + result += handle(f_code->co_name).cast(); + result += '\n'; + Py_DECREF(f_code); + auto *b_frame = PyFrame_GetBack(frame); + Py_DECREF(frame); + frame = b_frame; + } + + have_trace = true; +#endif //! defined(PYPY_VERSION) + } + + if (!message_error_string.empty()) { + if (!have_trace) { + result += '\n'; + } + result += "\nMESSAGE UNAVAILABLE DUE TO EXCEPTION: " + message_error_string; + } + + return result; +} + +PYBIND11_INLINE std::string const &error_fetch_and_normalize::error_string() const { + if (!m_lazy_error_string_completed) { + m_lazy_error_string += ": " + format_value_and_trace(); + m_lazy_error_string_completed = true; + } + return m_lazy_error_string; +} + +PYBIND11_INLINE void error_fetch_and_normalize::restore() { + if (m_restore_called) { + pybind11_fail("Internal error: pybind11::detail::error_fetch_and_normalize::restore() " + "called a second time. ORIGINAL ERROR: " + + error_string()); + } + PyErr_Restore(m_type.inc_ref().ptr(), m_value.inc_ref().ptr(), m_trace.inc_ref().ptr()); + m_restore_called = true; +} + +PYBIND11_INLINE std::string error_string() { + return error_fetch_and_normalize("pybind11::detail::error_string").error_string(); +} + +PYBIND11_NAMESPACE_END(detail) + +PYBIND11_INLINE void raise_from(PyObject *type, const char *message) { + // Based on _PyErr_FormatVFromCause: + // https://github.com/python/cpython/blob/467ab194fc6189d9f7310c89937c51abeac56839/Python/errors.c#L405 + // See https://github.com/pybind/pybind11/pull/2112 for details. + PyObject *exc = nullptr, *val = nullptr, *val2 = nullptr, *tb = nullptr; + + assert(PyErr_Occurred()); + PyErr_Fetch(&exc, &val, &tb); + PyErr_NormalizeException(&exc, &val, &tb); + if (tb != nullptr) { + PyException_SetTraceback(val, tb); + Py_DECREF(tb); + } + Py_DECREF(exc); + assert(!PyErr_Occurred()); + + PyErr_SetString(type, message); + + PyErr_Fetch(&exc, &val2, &tb); + PyErr_NormalizeException(&exc, &val2, &tb); + Py_INCREF(val); + PyException_SetCause(val2, val); + PyException_SetContext(val2, val); + PyErr_Restore(exc, val2, tb); +} + +PYBIND11_INLINE void raise_from(error_already_set &err, PyObject *type, const char *message) { + err.restore(); + raise_from(type, message); +} + +/// @cond DUPLICATE +PYBIND11_INLINE memoryview memoryview::from_buffer(void *ptr, + ssize_t itemsize, + const char *format, + detail::any_container shape, + detail::any_container strides, + bool readonly) { + size_t ndim = shape->size(); + if (ndim != strides->size()) { + pybind11_fail("memoryview: shape length doesn't match strides length"); + } + ssize_t size = ndim != 0u ? 1 : 0; + for (size_t i = 0; i < ndim; ++i) { + size *= (*shape)[i]; + } + Py_buffer view; + view.buf = ptr; + view.obj = nullptr; + view.len = size * itemsize; + view.readonly = static_cast(readonly); + view.itemsize = itemsize; + view.format = const_cast(format); + view.ndim = static_cast(ndim); + view.shape = shape->data(); + view.strides = strides->data(); + view.suboffsets = nullptr; + view.internal = nullptr; + PyObject *obj = PyMemoryView_FromBuffer(&view); + if (!obj) { + throw error_already_set(); + } + return memoryview(object(obj, stolen_t{})); +} +/// @endcond + +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 13a6ebaa4d..71cc31b044 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -528,194 +528,16 @@ struct error_fetch_and_normalize { // would be more complex. // Starting with Python 3.12, PyErr_Fetch() normalizes exceptions immediately. // Any errors during normalization are tracked under __notes__. - explicit error_fetch_and_normalize(const char *called) { - PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); - if (!m_type) { - pybind11_fail("Internal error: " + std::string(called) - + " called while " - "Python error indicator not set."); - } - const char *exc_type_name_orig = detail::obj_class_name(m_type.ptr()); - if (exc_type_name_orig == nullptr) { - pybind11_fail("Internal error: " + std::string(called) - + " failed to obtain the name " - "of the original active exception type."); - } - m_lazy_error_string = exc_type_name_orig; -#if PY_VERSION_HEX >= 0x030C0000 - // The presence of __notes__ is likely due to exception normalization - // errors, although that is not necessarily true, therefore insert a - // hint only: - const int has_notes = PyObject_HasAttrString(m_value.ptr(), "__notes__"); - if (has_notes == 1) { - m_lazy_error_string += "[WITH __notes__]"; - } else if (has_notes == -1) { - // Ignore secondary errors when probing for __notes__ to avoid leaking a - // spurious exception while still reporting the original error. - PyErr_Clear(); - } -#else - // PyErr_NormalizeException() may change the exception type if there are cascading - // failures. This can potentially be extremely confusing. - PyErr_NormalizeException(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); - if (m_type.ptr() == nullptr) { - pybind11_fail("Internal error: " + std::string(called) - + " failed to normalize the " - "active exception."); - } - const char *exc_type_name_norm = detail::obj_class_name(m_type.ptr()); - if (exc_type_name_norm == nullptr) { - pybind11_fail("Internal error: " + std::string(called) - + " failed to obtain the name " - "of the normalized active exception type."); - } - if (exc_type_name_norm != m_lazy_error_string) { - std::string msg = std::string(called) - + ": MISMATCH of original and normalized " - "active exception types: "; - msg += "ORIGINAL "; - msg += m_lazy_error_string; - msg += " REPLACED BY "; - msg += exc_type_name_norm; - msg += ": " + format_value_and_trace(); - pybind11_fail(msg); - } -#endif - } + explicit error_fetch_and_normalize(const char *called); error_fetch_and_normalize(const error_fetch_and_normalize &) = delete; error_fetch_and_normalize(error_fetch_and_normalize &&) = delete; - std::string format_value_and_trace() const { - std::string result; - std::string message_error_string; - if (m_value) { - auto value_str = reinterpret_steal(PyObject_Str(m_value.ptr())); - constexpr const char *message_unavailable_exc - = ""; - if (!value_str) { - message_error_string = detail::error_string(); - result = message_unavailable_exc; - } else { - // Not using `value_str.cast()`, to not potentially throw a secondary - // error_already_set that will then result in process termination (#4288). - auto value_bytes = reinterpret_steal( - PyUnicode_AsEncodedString(value_str.ptr(), "utf-8", "backslashreplace")); - if (!value_bytes) { - message_error_string = detail::error_string(); - result = message_unavailable_exc; - } else { - char *buffer = nullptr; - Py_ssize_t length = 0; - if (PyBytes_AsStringAndSize(value_bytes.ptr(), &buffer, &length) == -1) { - message_error_string = detail::error_string(); - result = message_unavailable_exc; - } else { - result = std::string(buffer, static_cast(length)); - } - } - } -#if PY_VERSION_HEX >= 0x030B0000 - auto notes - = reinterpret_steal(PyObject_GetAttrString(m_value.ptr(), "__notes__")); - if (!notes) { - PyErr_Clear(); // No notes is good news. - } else { - auto len_notes = PyList_Size(notes.ptr()); - if (len_notes < 0) { - result += "\nFAILURE obtaining len(__notes__): " + detail::error_string(); - } else { - result += "\n__notes__ (len=" + std::to_string(len_notes) + "):"; - for (ssize_t i = 0; i < len_notes; i++) { - PyObject *note = PyList_GET_ITEM(notes.ptr(), i); - auto note_bytes = reinterpret_steal( - PyUnicode_AsEncodedString(note, "utf-8", "backslashreplace")); - if (!note_bytes) { - result += "\nFAILURE obtaining __notes__[" + std::to_string(i) - + "]: " + detail::error_string(); - } else { - char *buffer = nullptr; - Py_ssize_t length = 0; - if (PyBytes_AsStringAndSize(note_bytes.ptr(), &buffer, &length) - == -1) { - result += "\nFAILURE formatting __notes__[" + std::to_string(i) - + "]: " + detail::error_string(); - } else { - result += '\n'; - result += std::string(buffer, static_cast(length)); - } - } - } - } - } -#endif - } else { - result = ""; - } - if (result.empty()) { - result = ""; - } - - bool have_trace = false; - if (m_trace) { -#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) - auto *tb = reinterpret_cast(m_trace.ptr()); - - // Get the deepest trace possible. - while (tb->tb_next) { - tb = tb->tb_next; - } - - PyFrameObject *frame = tb->tb_frame; - Py_XINCREF(frame); - result += "\n\nAt:\n"; - while (frame) { - PyCodeObject *f_code = PyFrame_GetCode(frame); - int lineno = PyFrame_GetLineNumber(frame); - result += " "; - result += handle(f_code->co_filename).cast(); - result += '('; - result += std::to_string(lineno); - result += "): "; - result += handle(f_code->co_name).cast(); - result += '\n'; - Py_DECREF(f_code); - auto *b_frame = PyFrame_GetBack(frame); - Py_DECREF(frame); - frame = b_frame; - } - - have_trace = true; -#endif //! defined(PYPY_VERSION) - } - - if (!message_error_string.empty()) { - if (!have_trace) { - result += '\n'; - } - result += "\nMESSAGE UNAVAILABLE DUE TO EXCEPTION: " + message_error_string; - } - - return result; - } + std::string format_value_and_trace() const; - std::string const &error_string() const { - if (!m_lazy_error_string_completed) { - m_lazy_error_string += ": " + format_value_and_trace(); - m_lazy_error_string_completed = true; - } - return m_lazy_error_string; - } + std::string const &error_string() const; - void restore() { - if (m_restore_called) { - pybind11_fail("Internal error: pybind11::detail::error_fetch_and_normalize::restore() " - "called a second time. ORIGINAL ERROR: " - + error_string()); - } - PyErr_Restore(m_type.inc_ref().ptr(), m_value.inc_ref().ptr(), m_trace.inc_ref().ptr()); - m_restore_called = true; - } + void restore(); bool matches(handle exc) const { return (PyErr_GivenExceptionMatches(m_type.ptr(), exc.ptr()) != 0); @@ -731,10 +553,6 @@ struct error_fetch_and_normalize { mutable bool m_restore_called = false; }; -inline std::string error_string() { - return error_fetch_and_normalize("pybind11::detail::error_string").error_string(); -} - PYBIND11_NAMESPACE_END(detail) /// Fetch and hold an error which was already set in Python. An instance of this is typically @@ -799,39 +617,12 @@ class PYBIND11_EXPORT_EXCEPTION error_already_set : public std::exception { /// Replaces the current Python error indicator with the chosen error, performing a /// 'raise from' to indicate that the chosen error was caused by the original error. -inline void raise_from(PyObject *type, const char *message) { - // Based on _PyErr_FormatVFromCause: - // https://github.com/python/cpython/blob/467ab194fc6189d9f7310c89937c51abeac56839/Python/errors.c#L405 - // See https://github.com/pybind/pybind11/pull/2112 for details. - PyObject *exc = nullptr, *val = nullptr, *val2 = nullptr, *tb = nullptr; - - assert(PyErr_Occurred()); - PyErr_Fetch(&exc, &val, &tb); - PyErr_NormalizeException(&exc, &val, &tb); - if (tb != nullptr) { - PyException_SetTraceback(val, tb); - Py_DECREF(tb); - } - Py_DECREF(exc); - assert(!PyErr_Occurred()); - - PyErr_SetString(type, message); - - PyErr_Fetch(&exc, &val2, &tb); - PyErr_NormalizeException(&exc, &val2, &tb); - Py_INCREF(val); - PyException_SetCause(val2, val); - PyException_SetContext(val2, val); - PyErr_Restore(exc, val2, tb); -} +void raise_from(PyObject *type, const char *message); /// Sets the current Python error indicator with the chosen error, performing a 'raise from' /// from the error contained in error_already_set to indicate that the chosen error was /// caused by the original error. -inline void raise_from(error_already_set &err, PyObject *type, const char *message) { - err.restore(); - raise_from(type, message); -} +void raise_from(error_already_set &err, PyObject *type, const char *message); /** \defgroup python_builtins const_name Unless stated otherwise, the following C++ functions behave the same @@ -2498,40 +2289,6 @@ class memoryview : public object { #endif }; -/// @cond DUPLICATE -inline memoryview memoryview::from_buffer(void *ptr, - ssize_t itemsize, - const char *format, - detail::any_container shape, - detail::any_container strides, - bool readonly) { - size_t ndim = shape->size(); - if (ndim != strides->size()) { - pybind11_fail("memoryview: shape length doesn't match strides length"); - } - ssize_t size = ndim != 0u ? 1 : 0; - for (size_t i = 0; i < ndim; ++i) { - size *= (*shape)[i]; - } - Py_buffer view; - view.buf = ptr; - view.obj = nullptr; - view.len = size * itemsize; - view.readonly = static_cast(readonly); - view.itemsize = itemsize; - view.format = const_cast(format); - view.ndim = static_cast(ndim); - view.shape = shape->data(); - view.strides = strides->data(); - view.suboffsets = nullptr; - view.internal = nullptr; - PyObject *obj = PyMemoryView_FromBuffer(&view); - if (!obj) { - throw error_already_set(); - } - return memoryview(object(obj, stolen_t{})); -} -/// @endcond /// @} pytypes /// \addtogroup python_builtins @@ -2724,3 +2481,7 @@ inline object get_module_name_if_available(handle scope) { PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_PRECOMPILED +# include "pytypes-inl.h" // IWYU pragma: export +#endif diff --git a/src/internals.cpp b/src/internals.cpp new file mode 100644 index 0000000000..86a3549507 --- /dev/null +++ b/src/internals.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 new file mode 100644 index 0000000000..751b0b1dbe --- /dev/null +++ b/src/pybind11_combined.cpp @@ -0,0 +1,16 @@ +// 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. + +// Single-TU build of the pybind11 library sources, for build systems that prefer adding +// one file over one file per header (e.g. setuptools). Compile this file (and every TU +// that includes pybind11) with PYBIND11_PRECOMPILED defined. Keep in sync with the list +// of -inl.h files; the CMake path compiles the individual src/*.cpp files instead. + +#if !defined(PYBIND11_PRECOMPILED) +# error "pybind11 library sources must be compiled with PYBIND11_PRECOMPILED defined." +#endif + +#include +#include +#include diff --git a/src/pytypes.cpp b/src/pytypes.cpp new file mode 100644 index 0000000000..c0d655da96 --- /dev/null +++ b/src/pytypes.cpp @@ -0,0 +1,12 @@ +// 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 + +// pybind11.h first: the -inl.h definitions instantiate templates (e.g. handle::cast) +// whose definitions live in other headers. +#include +#include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d6415b98bc..c23e7668e1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -71,6 +71,16 @@ option(DOWNLOAD_CATCH "Download catch2 if not found" OFF) option(DOWNLOAD_EIGEN "Download EIGEN" OFF) option(PYBIND11_CUDA_TESTS "Enable building CUDA tests" OFF) option(PYBIND11_TEST_SMART_HOLDER "Change the default to smart holder" OFF) +option(PYBIND11_TEST_PRECOMPILE "Build the test modules against the precompiled library" OFF) + +if(PYBIND11_TEST_PRECOMPILE) + # Picked up by every pybind11_add_module() call below; any definition missing + # from the precompiled library shows up as a link error across the test modules. + set(PYBIND11_PRECOMPILE ON) +endif() +# These modules redefine PYBIND11_INTERNALS_VERSION inside their own TU, so they +# cannot share the precompiled library. +set(PYBIND11_NO_PRECOMPILE_TARGETS exo_planet_pybind11 cross_module_gil_utils) set(PYBIND11_TEST_OVERRIDE "" CACHE STRING "Tests from ;-separated list of *.cpp files will be built instead of all tests") @@ -483,8 +493,14 @@ foreach(target ${test_targets}) set_property(SOURCE ${target}.cpp PROPERTY LANGUAGE CUDA) endif() + set(no_precompile_arg "") + if("${target}" IN_LIST PYBIND11_NO_PRECOMPILE_TARGETS) + set(no_precompile_arg NO_PRECOMPILE) + endif() + # Create the binding library - pybind11_add_module(${target} THIN_LTO ${target}.cpp ${test_files} ${PYBIND11_HEADERS}) + pybind11_add_module(${target} THIN_LTO ${no_precompile_arg} ${target}.cpp ${test_files} + ${PYBIND11_HEADERS}) pybind11_enable_warnings(${target}) if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 164611db34..b02892a738 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -62,6 +62,7 @@ "include/pybind11/operators.h", "include/pybind11/options.h", "include/pybind11/pybind11.h", + "include/pybind11/pytypes-inl.h", "include/pybind11/pytypes.h", "include/pybind11/subinterpreter.h", "include/pybind11/stl.h", @@ -90,6 +91,7 @@ "include/pybind11/detail/function_ref.h", "include/pybind11/detail/holder_caster_foreign_helpers.h", "include/pybind11/detail/init.h", + "include/pybind11/detail/internals-inl.h", "include/pybind11/detail/internals.h", "include/pybind11/detail/native_enum_data.h", "include/pybind11/detail/pybind11_namespace_macros.h", @@ -126,6 +128,14 @@ "share/pkgconfig/pybind11.pc", } +sdist_src_files = { + "src/internals.cpp", + "src/pybind11_combined.cpp", + "src/pytypes.cpp", +} + +src_files = {f"share/pybind11/{n}" for n in sdist_src_files} + py_files = { "__init__.py", "__main__.py", @@ -138,7 +148,7 @@ } headers = main_headers | conduit_headers | detail_headers | eigen_headers | stl_headers -generated_files = cmake_files | pkgconfig_files +generated_files = cmake_files | pkgconfig_files | src_files all_files = headers | generated_files | py_files sdist_files = { @@ -204,7 +214,7 @@ def test_build_sdist(monkeypatch, tmpdir): pyproject_toml = read_tz_file(tar, "pyproject.toml") pkg_info = read_tz_file(tar, pkg_info_path).decode("utf-8") - files = headers | sdist_files + files = headers | sdist_src_files | sdist_files assert files <= simpler assert b'name = "pybind11"' in pyproject_toml @@ -241,7 +251,7 @@ def test_build_global_dist(monkeypatch, tmpdir): pyproject_toml = read_tz_file(tar, "pyproject.toml") pkg_info = read_tz_file(tar, pkg_info_path).decode("utf-8") - files = headers | sdist_files + files = headers | sdist_src_files | sdist_files assert files <= simpler assert b'name = "pybind11-global"' in pyproject_toml diff --git a/tests/test_cmake_build/CMakeLists.txt b/tests/test_cmake_build/CMakeLists.txt index a4d25448e5..33a78a6b1d 100644 --- a/tests/test_cmake_build/CMakeLists.txt +++ b/tests/test_cmake_build/CMakeLists.txt @@ -69,6 +69,7 @@ possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID) pybind11_add_build_test(subdirectory_function) pybind11_add_build_test(subdirectory_target) +pybind11_add_build_test(subdirectory_precompile) if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy") @@ -86,6 +87,7 @@ if(PYBIND11_INSTALL) pybind11_add_build_test(installed_function INSTALL) endif() pybind11_add_build_test(installed_target INSTALL) + pybind11_add_build_test(installed_precompile INSTALL) if(NOT ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" diff --git a/tests/test_cmake_build/installed_precompile/CMakeLists.txt b/tests/test_cmake_build/installed_precompile/CMakeLists.txt new file mode 100644 index 0000000000..268aebc0ce --- /dev/null +++ b/tests/test_cmake_build/installed_precompile/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.15...4.2) + +project(test_installed_precompile CXX) + +find_package(pybind11 CONFIG REQUIRED) +message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}") + +pybind11_add_module(test_installed_precompile PRECOMPILE ../main.cpp) +set_target_properties(test_installed_precompile PROPERTIES OUTPUT_NAME test_cmake_build) + +if(DEFINED Python_EXECUTABLE) + set(_Python_EXECUTABLE "${Python_EXECUTABLE}") +elseif(DEFINED PYTHON_EXECUTABLE) + set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}") +else() + message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)") +endif() + +add_custom_target( + check_installed_precompile + ${CMAKE_COMMAND} + -E + env + PYTHONPATH=$ + ${_Python_EXECUTABLE} + ${PROJECT_SOURCE_DIR}/../test.py + ${PROJECT_NAME} + DEPENDS test_installed_precompile) diff --git a/tests/test_cmake_build/subdirectory_precompile/CMakeLists.txt b/tests/test_cmake_build/subdirectory_precompile/CMakeLists.txt new file mode 100644 index 0000000000..02e8c0f089 --- /dev/null +++ b/tests/test_cmake_build/subdirectory_precompile/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.15...4.2) + +project(test_subdirectory_precompile CXX) + +# Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests +# (makes transition easier while we support both modes). +if(DEFINED PYTHON_EXECUTABLE AND NOT DEFINED Python_EXECUTABLE) + set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}") +endif() + +add_subdirectory("${pybind11_SOURCE_DIR}" pybind11) +pybind11_add_module(test_subdirectory_precompile PRECOMPILE ../main.cpp) +set_target_properties(test_subdirectory_precompile PROPERTIES OUTPUT_NAME test_cmake_build) + +if(DEFINED Python_EXECUTABLE) + set(_Python_EXECUTABLE "${Python_EXECUTABLE}") +elseif(DEFINED PYTHON_EXECUTABLE) + set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}") +else() + message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)") +endif() + +add_custom_target( + check_subdirectory_precompile + ${CMAKE_COMMAND} + -E + env + PYTHONPATH=$ + ${_Python_EXECUTABLE} + ${PROJECT_SOURCE_DIR}/../test.py + ${PROJECT_NAME} + DEPENDS test_subdirectory_precompile) diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index d75fb67520..b59ad2afff 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -466,3 +466,75 @@ function(pybind11_strip target_name) COMMAND ${CMAKE_STRIP} ${x_opt} $) endif() endfunction() + +# -fvisibility=hidden is required to allow multiple modules compiled against +# different pybind versions to work properly, and for some features (e.g. +# py::module_local). We force it on everything inside the `pybind11` +# namespace; also turning it on for a pybind module compilation here avoids +# potential warnings or issues from having mixed hidden/non-hidden types. +function(_pybind11_default_hidden_visibility target_name) + if(NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) + set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden") + endif() + + if(NOT DEFINED CMAKE_CUDA_VISIBILITY_PRESET) + set_target_properties(${target_name} PROPERTIES CUDA_VISIBILITY_PRESET "hidden") + endif() +endfunction() + +# --------------------- pybind11_precompile ------------------------- + +# Create the pybind11::precompiled static library (once per build tree). It is +# built from the consumer's project with the consumer's flags; each extension +# module links its own copy, preserving pybind11's per-module state. Modules +# using it must compile with PYBIND11_PRECOMPILED, which the PUBLIC compile +# definition below provides automatically. +function(pybind11_precompile) + if(TARGET pybind11_precompiled) + return() + endif() + + if(PYBIND11_NOPYTHON) + message(FATAL_ERROR "pybind11_precompile requires Python headers; it cannot be used " + "with PYBIND11_NOPYTHON") + endif() + + if(NOT pybind11_SRC_DIR OR NOT EXISTS "${pybind11_SRC_DIR}") + message(FATAL_ERROR "pybind11 library sources not found (pybind11_SRC_DIR: " + "'${pybind11_SRC_DIR}')") + endif() + + # CONFIGURE_DEPENDS: an in-place pybind11 upgrade can add src files; a stale + # list would fail with confusing undefined symbols. + file(GLOB _pybind11_precompile_sources CONFIGURE_DEPENDS "${pybind11_SRC_DIR}/*.cpp") + list(FILTER _pybind11_precompile_sources EXCLUDE REGEX "pybind11_combined\\.cpp$") + + add_library(pybind11_precompiled STATIC EXCLUDE_FROM_ALL ${_pybind11_precompile_sources}) + add_library(pybind11::precompiled ALIAS pybind11_precompiled) + target_compile_definitions(pybind11_precompiled PUBLIC PYBIND11_PRECOMPILED) + target_link_libraries( + pybind11_precompiled + PUBLIC pybind11::headers + PRIVATE pybind11::pybind11) + set_target_properties(pybind11_precompiled PROPERTIES POSITION_INDEPENDENT_CODE ON) + _pybind11_default_hidden_visibility(pybind11_precompiled) + if(NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN) + set_target_properties(pybind11_precompiled PROPERTIES VISIBILITY_INLINES_HIDDEN ON) + endif() + if(MSVC) + target_link_libraries(pybind11_precompiled PRIVATE pybind11::windows_extras) + endif() + + # The first caller creates the library, so its directory scope supplies the + # library's flags and C++ standard; record it to make that visible. + message(STATUS "pybind11::precompiled created in ${CMAKE_CURRENT_SOURCE_DIR}") +endfunction() + +# Link pybind11::precompiled when the PRECOMPILE keyword or the global +# PYBIND11_PRECOMPILE variable requests it, unless NO_PRECOMPILE opts out. +function(_pybind11_maybe_precompile target_name precompile no_precompile) + if((precompile OR PYBIND11_PRECOMPILE) AND NOT no_precompile) + pybind11_precompile() + target_link_libraries(${target_name} PRIVATE pybind11::precompiled) + endif() +endfunction() diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index f52b2fb891..abcd43e199 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -211,6 +211,9 @@ Using ``find_package`` with version info is not recommended except for release v # This will be relative unless explicitly set as absolute set(pybind11_INCLUDE_DIR "@pybind11_INCLUDEDIR@") +# Location of the library sources for the opt-in precompiled mode +set(pybind11_SRC_DIR "@pybind11_SRCDIR@") + set(pybind11_LIBRARY "") set(pybind11_DEFINITIONS USING_pybind11) set(pybind11_VERSION_TYPE "@pybind11_VERSION_TYPE@") diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index b0fe20768d..175268d113 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -255,8 +255,10 @@ endif() # WITHOUT_SOABI and WITH_SOABI will disable the custom extension handling used by pybind11. # WITH_SOABI is passed on to python_add_library. function(pybind11_add_module target_name) - cmake_parse_arguments(PARSE_ARGV 1 ARG - "STATIC;SHARED;MODULE;THIN_LTO;OPT_SIZE;NO_EXTRAS;WITHOUT_SOABI" "" "") + cmake_parse_arguments( + PARSE_ARGV 1 ARG + "STATIC;SHARED;MODULE;THIN_LTO;OPT_SIZE;NO_EXTRAS;WITHOUT_SOABI;PRECOMPILE;NO_PRECOMPILE" "" + "") if(ARG_STATIC) set(lib_type STATIC) @@ -282,18 +284,9 @@ function(pybind11_add_module target_name) target_link_libraries(${target_name} PRIVATE pybind11::embed) endif() - # -fvisibility=hidden is required to allow multiple modules compiled against - # different pybind versions to work properly, and for some features (e.g. - # py::module_local). We force it on everything inside the `pybind11` - # namespace; also turning it on for a pybind module compilation here avoids - # potential warnings or issues from having mixed hidden/non-hidden types. - if(NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) - set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden") - endif() + _pybind11_maybe_precompile(${target_name} "${ARG_PRECOMPILE}" "${ARG_NO_PRECOMPILE}") - if(NOT DEFINED CMAKE_CUDA_VISIBILITY_PRESET) - set_target_properties(${target_name} PROPERTIES CUDA_VISIBILITY_PRESET "hidden") - endif() + _pybind11_default_hidden_visibility(${target_name}) # If we don't pass a WITH_SOABI or WITHOUT_SOABI, use our own default handling of extensions if(NOT ARG_WITHOUT_SOABI AND NOT "WITH_SOABI" IN_LIST ARG_UNPARSED_ARGUMENTS) diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 81faee7d8b..9a7e5c6217 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -139,7 +139,8 @@ endfunction() # [NO_EXTRAS] [THIN_LTO] [OPT_SIZE] source1 [source2 ...]) # function(pybind11_add_module target_name) - set(options "MODULE;SHARED;EXCLUDE_FROM_ALL;NO_EXTRAS;SYSTEM;THIN_LTO;OPT_SIZE") + set(options + "MODULE;SHARED;EXCLUDE_FROM_ALL;NO_EXTRAS;SYSTEM;THIN_LTO;OPT_SIZE;PRECOMPILE;NO_PRECOMPILE") cmake_parse_arguments(ARG "${options}" "" "" ${ARGN}) if(ARG_MODULE AND ARG_SHARED) @@ -160,6 +161,8 @@ function(pybind11_add_module target_name) target_link_libraries(${target_name} PRIVATE pybind11::module) + _pybind11_maybe_precompile(${target_name} "${ARG_PRECOMPILE}" "${ARG_NO_PRECOMPILE}") + if(ARG_SYSTEM) message( STATUS @@ -169,18 +172,7 @@ function(pybind11_add_module target_name) pybind11_extension(${target_name}) - # -fvisibility=hidden is required to allow multiple modules compiled against - # different pybind versions to work properly, and for some features (e.g. - # py::module_local). We force it on everything inside the `pybind11` - # namespace; also turning it on for a pybind module compilation here avoids - # potential warnings or issues from having mixed hidden/non-hidden types. - if(NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) - set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden") - endif() - - if(NOT DEFINED CMAKE_CUDA_VISIBILITY_PRESET) - set_target_properties(${target_name} PROPERTIES CUDA_VISIBILITY_PRESET "hidden") - endif() + _pybind11_default_hidden_visibility(${target_name}) if(ARG_NO_EXTRAS) return() From 9eb42669e722e0c65e906ad3e78f2bb5e7d11f45 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 23:25:07 -0400 Subject: [PATCH 03/14] fix(tidy): keep the tidy preset header-only In precompiled mode the -inl.h definitions are intentionally non-inline, so misc-definitions-in-headers fires on every one. The header-only tidy build already analyzes all -inl.h bodies via the bottom-of-header includes. Assisted-by: ClaudeCode:claude-fable-5 --- CMakePresets.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 6e86aa331f..42bf3ade9d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -36,8 +36,7 @@ "binaryDir": "build-tidy", "cacheVariables": { "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--use-color;--warnings-as-errors=*", - "CMAKE_CXX_STANDARD": "17", - "PYBIND11_TEST_PRECOMPILE": "ON" + "CMAKE_CXX_STANDARD": "17" } } ], From 86a984fc2f73f4bf0e530f749d17f25332ae805a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 23:32:21 -0400 Subject: [PATCH 04/14] fix(cmake): compile the precompiled library with the interpreter ABI macros pybind11::pybind11 only carries headers; Py_GIL_DISABLED lives on Python::Module via pybind11::module. Without it the library is ABI-mismatched on free-threaded builds, and on Windows the pyconfig.h autolink pragma in its objects requests pythonXY.lib instead of pythonXYt.lib. Assisted-by: ClaudeCode:claude-fable-5 --- tools/pybind11Common.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index b59ad2afff..0073d3634d 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -512,10 +512,13 @@ function(pybind11_precompile) add_library(pybind11_precompiled STATIC EXCLUDE_FROM_ALL ${_pybind11_precompile_sources}) add_library(pybind11::precompiled ALIAS pybind11_precompiled) target_compile_definitions(pybind11_precompiled PUBLIC PYBIND11_PRECOMPILED) + # pybind11::module (not just pybind11::pybind11): the library must compile with the + # interpreter's ABI macros (e.g. Py_GIL_DISABLED, which FindPython attaches to + # Python::Module); on free-threaded Windows they select the correct autolink library. target_link_libraries( pybind11_precompiled PUBLIC pybind11::headers - PRIVATE pybind11::pybind11) + PRIVATE pybind11::module) set_target_properties(pybind11_precompiled PROPERTIES POSITION_INDEPENDENT_CODE ON) _pybind11_default_hidden_visibility(pybind11_precompiled) if(NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN) From b6e12f044053323cbfc697f39b0126e1a07ab118 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 22:21:52 -0400 Subject: [PATCH 05/14] feat: move detail/class.h definitions to class-inl.h All 32 functions in detail/class.h are non-template plumbing (type/slot machinery); move them out of line for the precompiled mode. Forward declarations of these functions in other headers lose their inline keyword to stay ODR-consistent in both modes. Assisted-by: ClaudeCode:claude-fable-5 --- CMakeLists.txt | 1 + include/pybind11/detail/class-inl.h | 783 ++++++++++++++++++ include/pybind11/detail/class.h | 768 +---------------- include/pybind11/detail/cpp_conduit.h | 2 +- include/pybind11/detail/internals.h | 6 +- include/pybind11/detail/type_caster_base.h | 4 +- .../pybind11/trampoline_self_life_support.h | 2 +- src/class.cpp | 10 + src/pybind11_combined.cpp | 1 + tests/extra_python_package/test_files.py | 2 + 10 files changed, 847 insertions(+), 732 deletions(-) create mode 100644 include/pybind11/detail/class-inl.h create mode 100644 src/class.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b65529c9b0..3157a57e1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,7 @@ endif() set(PYBIND11_HEADERS include/pybind11/detail/argument_vector.h + include/pybind11/detail/class-inl.h include/pybind11/detail/class.h include/pybind11/detail/common.h include/pybind11/detail/cpp_conduit.h diff --git a/include/pybind11/detail/class-inl.h b/include/pybind11/detail/class-inl.h new file mode 100644 index 0000000000..57d12fee44 --- /dev/null +++ b/include/pybind11/detail/class-inl.h @@ -0,0 +1,783 @@ +/* + pybind11/detail/class-inl.h: Out-of-line definitions for class.h + + Copyright (c) 2017 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 class.h; when PYBIND11_PRECOMPILED is defined it is only +// compiled into the pybind11 static library (see src/). + +#pragma once + +#include "class.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +PYBIND11_INLINE std::string get_fully_qualified_tp_name(PyTypeObject *type) { +#if !defined(PYPY_VERSION) + return type->tp_name; +#else + auto module_name = handle((PyObject *) type).attr("__module__").cast(); + if (module_name == PYBIND11_BUILTINS_MODULE) + return type->tp_name; + else + return std::move(module_name) + "." + type->tp_name; +#endif +} + +PYBIND11_INLINE PyTypeObject *type_incref(PyTypeObject *type) { + Py_INCREF(type); + return type; +} + +#if !defined(PYPY_VERSION) +extern "C" PYBIND11_INLINE PyObject * +pybind11_static_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) { + return PyProperty_Type.tp_descr_get(self, cls, cls); +} + +extern "C" PYBIND11_INLINE int +pybind11_static_set(PyObject *self, PyObject *obj, PyObject *value) { + PyObject *cls = PyType_Check(obj) ? obj : (PyObject *) Py_TYPE(obj); + return PyProperty_Type.tp_descr_set(self, cls, value); +} + +PYBIND11_INLINE PyTypeObject *make_static_property_type() { + constexpr auto *name = "pybind11_static_property"; + auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); + + /* Danger zone: from now (and until PyType_Ready), make sure to + issue no Python C API calls which could potentially invoke the + garbage collector (the GC will call type_traverse(), which will in + turn find the newly constructed type in an invalid state) */ + auto *heap_type = reinterpret_cast(PyType_Type.tp_alloc(&PyType_Type, 0)); + if (!heap_type) { + pybind11_fail("make_static_property_type(): error allocating type!"); + } + + heap_type->ht_name = name_obj.inc_ref().ptr(); +# ifdef PYBIND11_BUILTIN_QUALNAME + heap_type->ht_qualname = name_obj.inc_ref().ptr(); +# endif + + auto *type = &heap_type->ht_type; + type->tp_name = name; + type->tp_base = type_incref(&PyProperty_Type); + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; + type->tp_descr_get = pybind11_static_get; + type->tp_descr_set = pybind11_static_set; + +# if PY_VERSION_HEX >= 0x030C0000 + // Since Python-3.12 property-derived types are required to + // have dynamic attributes (to set `__doc__`) + enable_dynamic_attributes(heap_type); +# endif + + if (PyType_Ready(type) < 0) { + pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); + } + + setattr(reinterpret_cast(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME)); + PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); + + return type; +} + +#else // PYPY +PYBIND11_INLINE PyTypeObject *make_static_property_type() { + auto d = dict(); + PyObject *result = PyRun_String(R"(\ +class pybind11_static_property(property): + def __get__(self, obj, cls): + return property.__get__(self, cls, cls) + + def __set__(self, obj, value): + cls = obj if isinstance(obj, type) else type(obj) + property.__set__(self, cls, value) +)", + Py_file_input, + d.ptr(), + d.ptr()); + if (result == nullptr) + throw error_already_set(); + Py_DECREF(result); + return (PyTypeObject *) d["pybind11_static_property"].cast().release().ptr(); +} + +#endif // PYPY +extern "C" PYBIND11_INLINE int +pybind11_meta_setattro(PyObject *obj, PyObject *name, PyObject *value) { + // Use `_PyType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw + // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`). + PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name); + + // The following assignment combinations are possible: + // 1. `Type.static_prop = value` --> descr_set: `Type.static_prop.__set__(value)` + // 2. `Type.static_prop = other_static_prop` --> setattro: replace existing `static_prop` + // 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment + auto *const static_prop = (PyObject *) get_internals().static_property_type; + const auto call_descr_set = (descr != nullptr) && (value != nullptr) + && (PyObject_IsInstance(descr, static_prop) != 0) + && (PyObject_IsInstance(value, static_prop) == 0); + if (call_descr_set) { + // Call `static_property.__set__()` instead of replacing the `static_property`. +#if !defined(PYPY_VERSION) + return Py_TYPE(descr)->tp_descr_set(descr, obj, value); +#else + if (PyObject *result = PyObject_CallMethod(descr, "__set__", "OO", obj, value)) { + Py_DECREF(result); + return 0; + } else { + return -1; + } +#endif + } else { + // Replace existing attribute. + return PyType_Type.tp_setattro(obj, name, value); + } +} + +extern "C" PYBIND11_INLINE PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name) { + PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name); + if (descr && PyInstanceMethod_Check(descr)) { + Py_INCREF(descr); + return descr; + } + return PyType_Type.tp_getattro(obj, name); +} + +extern "C" PYBIND11_INLINE PyObject * +pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs) { + + // use the default metaclass call to create/initialize the object + PyObject *self = PyType_Type.tp_call(type, args, kwargs); + if (self == nullptr) { + return nullptr; + } + + // Ensure that the base __init__ function(s) were called + values_and_holders vhs(self); + for (const auto &vh : vhs) { + if (!vh.holder_constructed() && !vhs.is_redundant_value_and_holder(vh)) { + PyErr_Format(PyExc_TypeError, + "%.200s.__init__() must be called when overriding __init__", + get_fully_qualified_tp_name(vh.type->type).c_str()); + Py_DECREF(self); + return nullptr; + } + } + + return self; +} + +extern "C" PYBIND11_INLINE void pybind11_meta_dealloc(PyObject *obj) { + with_internals_if_internals([obj](internals &internals) { + auto *type = (PyTypeObject *) obj; + + // A pybind11-registered type will: + // 1) be found in internals.registered_types_py + // 2) have exactly one associated `detail::type_info` + auto found_type = internals.registered_types_py.find(type); + if (found_type != internals.registered_types_py.end() && found_type->second.size() == 1 + && found_type->second[0]->type == type) { + + auto *tinfo = found_type->second[0]; + auto tindex = std::type_index(*tinfo->cpptype); + internals.direct_conversions.erase(tindex); + + auto &local_internals = get_local_internals(); + if (tinfo->module_local) { + 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); + for (const std::type_info *alias : tinfo->alias_chain) { + auto num_erased = internals.registered_types_cpp_fast.erase(alias); + (void) num_erased; + assert(num_erased > 0); + } +#endif + } + internals.registered_types_py.erase(tinfo->type); + + // Actually just `std::erase_if`, but that's only available in C++20 + auto &cache = internals.inactive_override_cache; + for (auto it = cache.begin(), last = cache.end(); it != last;) { + if (it->first == (PyObject *) tinfo->type) { + it = cache.erase(it); + } else { + ++it; + } + } + + delete tinfo; + } + }); + + PyType_Type.tp_dealloc(obj); +} + +PYBIND11_INLINE PyTypeObject *make_default_metaclass() { + constexpr auto *name = "pybind11_type"; + auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); + + /* Danger zone: from now (and until PyType_Ready), make sure to + issue no Python C API calls which could potentially invoke the + garbage collector (the GC will call type_traverse(), which will in + turn find the newly constructed type in an invalid state) */ + auto *heap_type = reinterpret_cast(PyType_Type.tp_alloc(&PyType_Type, 0)); + if (!heap_type) { + pybind11_fail("make_default_metaclass(): error allocating metaclass!"); + } + + heap_type->ht_name = name_obj.inc_ref().ptr(); +#ifdef PYBIND11_BUILTIN_QUALNAME + heap_type->ht_qualname = name_obj.inc_ref().ptr(); +#endif + + auto *type = &heap_type->ht_type; + type->tp_name = name; + type->tp_base = type_incref(&PyType_Type); + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; + + type->tp_call = pybind11_meta_call; + + type->tp_setattro = pybind11_meta_setattro; + type->tp_getattro = pybind11_meta_getattro; + + type->tp_dealloc = pybind11_meta_dealloc; + + if (PyType_Ready(type) < 0) { + pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!"); + } + + setattr(reinterpret_cast(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME)); + PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); + + return type; +} + +PYBIND11_INLINE void traverse_offset_bases(void *valueptr, + const detail::type_info *tinfo, + instance *self, + bool (*f)(void * /*parentptr*/, instance * /*self*/)) { + for (handle h : reinterpret_borrow(tinfo->type->tp_bases)) { + if (auto *parent_tinfo = get_type_info(reinterpret_cast(h.ptr()))) { + for (auto &c : parent_tinfo->implicit_casts) { + if (c.first == tinfo->cpptype) { + auto *parentptr = c.second(valueptr); + if (parentptr != valueptr) { + f(parentptr, self); + } + traverse_offset_bases(parentptr, parent_tinfo, self, f); + break; + } + } + } + } +} + +#ifdef Py_GIL_DISABLED +PYBIND11_INLINE void enable_try_inc_ref(PyObject *obj) { +# if PY_VERSION_HEX >= 0x030E00A4 + PyUnstable_EnableTryIncRef(obj); +# else + if (_Py_IsImmortal(obj)) { + return; + } + for (;;) { + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); + if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) { + // Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states. + return; + } + if (_Py_atomic_compare_exchange_ssize( + &obj->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) { + return; + } + } +# endif +} + +#endif +PYBIND11_INLINE bool register_instance_impl(void *ptr, instance *self) { + assert(ptr); +#ifdef Py_GIL_DISABLED + enable_try_inc_ref(reinterpret_cast(self)); +#endif + with_instance_map(ptr, [&](instance_map &instances) { instances.emplace(ptr, self); }); + return true; // unused, but gives the same signature as the deregister func +} + +PYBIND11_INLINE bool deregister_instance_impl(void *ptr, instance *self) { + assert(ptr); + return with_instance_map(ptr, [&](instance_map &instances) { + auto range = instances.equal_range(ptr); + for (auto it = range.first; it != range.second; ++it) { + if (self == it->second) { + instances.erase(it); + return true; + } + } + return false; + }); +} + +PYBIND11_INLINE void register_instance(instance *self, void *valptr, const type_info *tinfo) { + register_instance_impl(valptr, self); + if (!tinfo->simple_ancestors) { + traverse_offset_bases(valptr, tinfo, self, register_instance_impl); + } +} + +PYBIND11_INLINE bool deregister_instance(instance *self, void *valptr, const type_info *tinfo) { + bool ret = deregister_instance_impl(valptr, self); + if (!tinfo->simple_ancestors) { + traverse_offset_bases(valptr, tinfo, self, deregister_instance_impl); + } + return ret; +} + +PYBIND11_INLINE PyObject *make_new_instance(PyTypeObject *type) { +#if defined(PYPY_VERSION) + // PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first + // inherited object is a plain Python type (i.e. not derived from an extension type). Fix it. + ssize_t instance_size = static_cast(sizeof(instance)); + if (type->tp_basicsize < instance_size) { + type->tp_basicsize = instance_size; + } +#endif + PyObject *self = type->tp_alloc(type, 0); + auto *inst = reinterpret_cast(self); + // Allocate the value/holder internals: + inst->allocate_layout(); + + return self; +} + +extern "C" PYBIND11_INLINE PyObject * +pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) { + return make_new_instance(type); +} + +extern "C" PYBIND11_INLINE int pybind11_object_init(PyObject *self, PyObject *, PyObject *) { + PyTypeObject *type = Py_TYPE(self); + std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!"; + set_error(PyExc_TypeError, msg.c_str()); + return -1; +} + +PYBIND11_INLINE void add_patient(PyObject *nurse, PyObject *patient) { + auto *instance = reinterpret_cast(nurse); + instance->has_patients = true; + Py_INCREF(patient); + + with_internals([&](internals &internals) { internals.patients[nurse].push_back(patient); }); +} + +PYBIND11_INLINE void clear_patients(PyObject *self) { + auto *instance = reinterpret_cast(self); + std::vector patients; + + with_internals([&](internals &internals) { + auto pos = internals.patients.find(self); + + if (pos == internals.patients.end()) { + pybind11_fail( + "FATAL: Internal consistency check failed: Invalid clear_patients() call."); + } + + // Clearing the patients can cause more Python code to run, which + // can invalidate the iterator. Extract the vector of patients + // from the unordered_map first. + patients = std::move(pos->second); + internals.patients.erase(pos); + }); + + instance->has_patients = false; + for (PyObject *&patient : patients) { + Py_CLEAR(patient); + } +} + +PYBIND11_INLINE void clear_instance(PyObject *self) { + auto *instance = reinterpret_cast(self); + + // Deallocate any values/holders, if present: + for (auto &v_h : values_and_holders(instance)) { + if (v_h) { + + // We have to deregister before we call dealloc because, for virtual MI types, we still + // need to be able to get the parent pointers. + if (v_h.instance_registered() + && !deregister_instance(instance, v_h.value_ptr(), v_h.type)) { + pybind11_fail( + "pybind11_object_dealloc(): Tried to deallocate unregistered instance!"); + } + + if (instance->owned || v_h.holder_constructed()) { + v_h.type->dealloc(v_h); + } + } else if (v_h.holder_constructed()) { + v_h.type->dealloc(v_h); // Disowned instance. + } + } + // Deallocate the value/holder layout internals: + instance->deallocate_layout(); + + if (instance->weakrefs) { + PyObject_ClearWeakRefs(self); + } + + PyObject **dict_ptr = _PyObject_GetDictPtr(self); + if (dict_ptr) { + Py_CLEAR(*dict_ptr); + } + + if (instance->has_patients) { + clear_patients(self); + } +} + +extern "C" PYBIND11_INLINE void pybind11_object_dealloc(PyObject *self) { + auto *type = Py_TYPE(self); + + // If this is a GC tracked object, untrack it first + // Note that the track call is implicitly done by the + // default tp_alloc, which we never override. + if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC) != 0) { + PyObject_GC_UnTrack(self); + } + +#if PY_VERSION_HEX >= 0x030D0000 + // PyObject_ClearManagedDict() is available from Python 3.13+. It must be + // called before tp_free() because on Python 3.14+ tp_free no longer + // implicitly clears the managed dict, which would abandon the refcounts of + // objects stored in __dict__ of py::dynamic_attr() types, causing permanent + // memory leaks. + if (PyType_HasFeature(type, Py_TPFLAGS_MANAGED_DICT)) { + PyObject_ClearManagedDict(self); + } +#endif + + clear_instance(self); + + type->tp_free(self); + + // This was not needed before Python 3.8 (Python issue 35810) + // https://github.com/pybind/pybind11/issues/1946 + Py_DECREF(type); +} + +PYBIND11_INLINE PyObject *make_object_base_type(PyTypeObject *metaclass) { + constexpr auto *name = "pybind11_object"; + auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); + + /* Danger zone: from now (and until PyType_Ready), make sure to + issue no Python C API calls which could potentially invoke the + garbage collector (the GC will call type_traverse(), which will in + turn find the newly constructed type in an invalid state) */ + auto *heap_type = reinterpret_cast(metaclass->tp_alloc(metaclass, 0)); + if (!heap_type) { + pybind11_fail("make_object_base_type(): error allocating type!"); + } + + heap_type->ht_name = name_obj.inc_ref().ptr(); +#ifdef PYBIND11_BUILTIN_QUALNAME + heap_type->ht_qualname = name_obj.inc_ref().ptr(); +#endif + + auto *type = &heap_type->ht_type; + type->tp_name = name; + type->tp_base = type_incref(&PyBaseObject_Type); + type->tp_basicsize = static_cast(sizeof(instance)); + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; + + type->tp_new = pybind11_object_new; + type->tp_init = pybind11_object_init; + type->tp_dealloc = pybind11_object_dealloc; + + /* Support weak references (needed for the keep_alive feature) */ + type->tp_weaklistoffset = offsetof(instance, weakrefs); + + if (PyType_Ready(type) < 0) { + pybind11_fail("PyType_Ready failed in make_object_base_type(): " + error_string()); + } + + setattr(reinterpret_cast(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME)); + PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); + + assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)); + return reinterpret_cast(heap_type); +} + +extern "C" PYBIND11_INLINE int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { +#if PY_VERSION_HEX >= 0x030D0000 + int ret = PyObject_VisitManagedDict(self, visit, arg); + if (ret) { + return ret; + } +#else + PyObject *&dict = *_PyObject_GetDictPtr(self); + Py_VISIT(dict); +#endif + // https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse + Py_VISIT(Py_TYPE(self)); + return 0; +} + +extern "C" PYBIND11_INLINE int pybind11_clear(PyObject *self) { +#if PY_VERSION_HEX >= 0x030D0000 + PyObject_ClearManagedDict(self); +#else + PyObject *&dict = *_PyObject_GetDictPtr(self); + Py_CLEAR(dict); +#endif + return 0; +} + +PYBIND11_INLINE void enable_dynamic_attributes(PyHeapTypeObject *heap_type) { + auto *type = &heap_type->ht_type; + type->tp_flags |= Py_TPFLAGS_HAVE_GC; +#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET + type->tp_dictoffset = type->tp_basicsize; // place dict at the end + type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it +#else + type->tp_flags |= Py_TPFLAGS_MANAGED_DICT; +#endif + type->tp_traverse = pybind11_traverse; + type->tp_clear = pybind11_clear; + + static PyGetSetDef getset[] + = {{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr}}; + type->tp_getset = getset; +} + +extern "C" PYBIND11_INLINE int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int flags) { + // Look for a `get_buffer` implementation in this type's info or any bases (following MRO). + type_info *tinfo = nullptr; + for (auto type : reinterpret_borrow(Py_TYPE(obj)->tp_mro)) { + tinfo = get_type_info((PyTypeObject *) type.ptr()); + if (tinfo && tinfo->get_buffer) { + break; + } + } + if (view == nullptr || !tinfo || !tinfo->get_buffer) { + if (view) { + view->obj = nullptr; + } + set_error(PyExc_BufferError, "pybind11_getbuffer(): Internal error"); + return -1; + } + std::memset(view, 0, sizeof(Py_buffer)); + std::unique_ptr info = nullptr; + try { + info.reset(tinfo->get_buffer(obj, tinfo->get_buffer_data)); + } catch (...) { + try_translate_exceptions(); + raise_from(PyExc_BufferError, "Error getting buffer"); + return -1; + } + if (info == nullptr) { + pybind11_fail("FATAL UNEXPECTED SITUATION: tinfo->get_buffer() returned nullptr."); + } + + if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) { + // view->obj = nullptr; // Was just memset to 0, so not necessary + set_error(PyExc_BufferError, "Writable buffer requested for readonly storage"); + return -1; + } + + // Fill in all the information, and then downgrade as requested by the caller, or raise an + // error if that's not possible. + view->itemsize = info->itemsize; + view->len = view->itemsize; + for (auto s : info->shape) { + view->len *= s; + } + view->ndim = static_cast(info->ndim); + view->shape = info->shape.data(); + view->strides = info->strides.data(); + view->readonly = static_cast(info->readonly); + if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) { + view->format = const_cast(info->format.c_str()); + } + + // Note, all contiguity flags imply PyBUF_STRIDES and lower. + if ((flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) { + if (PyBuffer_IsContiguous(view, 'C') == 0) { + std::memset(view, 0, sizeof(Py_buffer)); + set_error(PyExc_BufferError, + "C-contiguous buffer requested for discontiguous storage"); + return -1; + } + } else if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) { + if (PyBuffer_IsContiguous(view, 'F') == 0) { + std::memset(view, 0, sizeof(Py_buffer)); + set_error(PyExc_BufferError, + "Fortran-contiguous buffer requested for discontiguous storage"); + return -1; + } + } else if ((flags & PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS) { + if (PyBuffer_IsContiguous(view, 'A') == 0) { + std::memset(view, 0, sizeof(Py_buffer)); + set_error(PyExc_BufferError, "Contiguous buffer requested for discontiguous storage"); + return -1; + } + + } else if ((flags & PyBUF_STRIDES) != PyBUF_STRIDES) { + // If no strides are requested, the buffer must be C-contiguous. + // https://docs.python.org/3/c-api/buffer.html#contiguity-requests + if (PyBuffer_IsContiguous(view, 'C') == 0) { + std::memset(view, 0, sizeof(Py_buffer)); + set_error(PyExc_BufferError, + "C-contiguous buffer requested for discontiguous storage"); + return -1; + } + + view->strides = nullptr; + + // Since this is a contiguous buffer, it can also pretend to be 1D. + if ((flags & PyBUF_ND) != PyBUF_ND) { + view->shape = nullptr; + view->ndim = 0; + } + } + + // Set these after all checks so they don't leak out into the caller, and can be automatically + // cleaned up on error. + view->buf = info->ptr; + view->internal = info.release(); + view->obj = obj; + Py_INCREF(view->obj); + return 0; +} + +extern "C" PYBIND11_INLINE void pybind11_releasebuffer(PyObject *, Py_buffer *view) { + delete (buffer_info *) view->internal; +} + +PYBIND11_INLINE void enable_buffer_protocol(PyHeapTypeObject *heap_type) { + heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer; + + heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer; + heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer; +} + +PYBIND11_INLINE PyObject *make_new_python_type(const type_record &rec) { + auto name = reinterpret_steal(PYBIND11_FROM_STRING(rec.name)); + + auto qualname = name; + if (rec.scope && !PyModule_Check(rec.scope.ptr()) && hasattr(rec.scope, "__qualname__")) { + qualname = reinterpret_steal( + PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr())); + } + + object module_ = get_module_name_if_available(rec.scope); + const auto *full_name = c_str( +#if !defined(PYPY_VERSION) + module_ ? str(module_).cast() + "." + rec.name : +#endif + rec.name); + + char *tp_doc = nullptr; + if (rec.doc && options::show_user_defined_docstrings()) { + /* Allocate memory for docstring (Python will free this later on) */ + size_t size = std::strlen(rec.doc) + 1; +#if PY_VERSION_HEX >= 0x030D0000 + tp_doc = static_cast(PyMem_MALLOC(size)); +#else + tp_doc = (char *) PyObject_MALLOC(size); +#endif + std::memcpy((void *) tp_doc, rec.doc, size); + } + + auto &internals = get_internals(); + auto bases = tuple(rec.bases); + auto *base = (bases.empty()) ? internals.instance_base : bases[0].ptr(); + + /* Danger zone: from now (and until PyType_Ready), make sure to + issue no Python C API calls which could potentially invoke the + garbage collector (the GC will call type_traverse(), which will in + turn find the newly constructed type in an invalid state) */ + auto *metaclass = rec.metaclass.ptr() ? reinterpret_cast(rec.metaclass.ptr()) + : internals.default_metaclass; + + auto *heap_type = reinterpret_cast(metaclass->tp_alloc(metaclass, 0)); + if (!heap_type) { + pybind11_fail(std::string(rec.name) + ": Unable to create type object!"); + } + + heap_type->ht_name = name.release().ptr(); +#ifdef PYBIND11_BUILTIN_QUALNAME + heap_type->ht_qualname = qualname.inc_ref().ptr(); +#endif + + auto *type = &heap_type->ht_type; + type->tp_name = full_name; + type->tp_doc = tp_doc; + type->tp_base = type_incref(reinterpret_cast(base)); + type->tp_basicsize = static_cast(sizeof(instance)); + if (!bases.empty()) { + type->tp_bases = bases.release().ptr(); + } + + /* Don't inherit base __init__ */ + type->tp_init = pybind11_object_init; + + /* Supported protocols */ + type->tp_as_number = &heap_type->as_number; + type->tp_as_sequence = &heap_type->as_sequence; + type->tp_as_mapping = &heap_type->as_mapping; + type->tp_as_async = &heap_type->as_async; + + /* Flags */ + type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE; + if (!rec.is_final) { + type->tp_flags |= Py_TPFLAGS_BASETYPE; + } + + if (rec.dynamic_attr) { + enable_dynamic_attributes(heap_type); + } + + if (rec.buffer_protocol) { + enable_buffer_protocol(heap_type); + } + + if (rec.custom_type_setup_callback) { + rec.custom_type_setup_callback(heap_type); + } + + if (PyType_Ready(type) < 0) { + pybind11_fail(std::string(rec.name) + ": PyType_Ready failed: " + error_string()); + } + + assert(!rec.dynamic_attr || PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)); + + /* Register type with the parent scope */ + if (rec.scope) { + setattr(rec.scope, rec.name, reinterpret_cast(type)); + } else { + Py_INCREF(type); // Keep it alive forever (reference leak) + } + + if (module_) { // Needed by pydoc + setattr(reinterpret_cast(type), "__module__", module_); + } + + PYBIND11_SET_OLDPY_QUALNAME(type, qualname); + + return reinterpret_cast(type); +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 06a761de54..2ecde4fbf4 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -27,107 +27,29 @@ PYBIND11_NAMESPACE_BEGIN(detail) setattr((PyObject *) obj, "__qualname__", nameobj) #endif -inline std::string get_fully_qualified_tp_name(PyTypeObject *type) { -#if !defined(PYPY_VERSION) - return type->tp_name; -#else - auto module_name = handle((PyObject *) type).attr("__module__").cast(); - if (module_name == PYBIND11_BUILTINS_MODULE) - return type->tp_name; - else - return std::move(module_name) + "." + type->tp_name; -#endif -} +std::string get_fully_qualified_tp_name(PyTypeObject *type); -inline PyTypeObject *type_incref(PyTypeObject *type) { - Py_INCREF(type); - return type; -} +PyTypeObject *type_incref(PyTypeObject *type); #if !defined(PYPY_VERSION) /// `pybind11_static_property.__get__()`: Always pass the class instead of the instance. -extern "C" inline PyObject *pybind11_static_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) { - return PyProperty_Type.tp_descr_get(self, cls, cls); -} +extern "C" PyObject *pybind11_static_get(PyObject *self, PyObject * /*ob*/, PyObject *cls); /// `pybind11_static_property.__set__()`: Just like the above `__get__()`. -extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObject *value) { - PyObject *cls = PyType_Check(obj) ? obj : (PyObject *) Py_TYPE(obj); - return PyProperty_Type.tp_descr_set(self, cls, value); -} - -// Forward declaration to use in `make_static_property_type()` -inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type); +extern "C" int pybind11_static_set(PyObject *self, PyObject *obj, PyObject *value); /** A `static_property` is the same as a `property` but the `__get__()` and `__set__()` methods are modified to always use the object type instead of a concrete instance. Return value: New reference. */ -inline PyTypeObject *make_static_property_type() { - constexpr auto *name = "pybind11_static_property"; - auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); - - /* Danger zone: from now (and until PyType_Ready), make sure to - issue no Python C API calls which could potentially invoke the - garbage collector (the GC will call type_traverse(), which will in - turn find the newly constructed type in an invalid state) */ - auto *heap_type = reinterpret_cast(PyType_Type.tp_alloc(&PyType_Type, 0)); - if (!heap_type) { - pybind11_fail("make_static_property_type(): error allocating type!"); - } - - heap_type->ht_name = name_obj.inc_ref().ptr(); -# ifdef PYBIND11_BUILTIN_QUALNAME - heap_type->ht_qualname = name_obj.inc_ref().ptr(); -# endif - - auto *type = &heap_type->ht_type; - type->tp_name = name; - type->tp_base = type_incref(&PyProperty_Type); - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; - type->tp_descr_get = pybind11_static_get; - type->tp_descr_set = pybind11_static_set; - -# if PY_VERSION_HEX >= 0x030C0000 - // Since Python-3.12 property-derived types are required to - // have dynamic attributes (to set `__doc__`) - enable_dynamic_attributes(heap_type); -# endif - - if (PyType_Ready(type) < 0) { - pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); - } - - setattr(reinterpret_cast(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME)); - PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); - - return type; -} +PyTypeObject *make_static_property_type(); #else // PYPY /** PyPy has some issues with the above C API, so we evaluate Python code instead. This function will only be called once so performance isn't really a concern. Return value: New reference. */ -inline PyTypeObject *make_static_property_type() { - auto d = dict(); - PyObject *result = PyRun_String(R"(\ -class pybind11_static_property(property): - def __get__(self, obj, cls): - return property.__get__(self, cls, cls) - - def __set__(self, obj, value): - cls = obj if isinstance(obj, type) else type(obj) - property.__set__(self, cls, value) -)", - Py_file_input, - d.ptr(), - d.ptr()); - if (result == nullptr) - throw error_already_set(); - Py_DECREF(result); - return (PyTypeObject *) d["pybind11_static_property"].cast().release().ptr(); -} +PyTypeObject *make_static_property_type(); #endif // PYPY @@ -135,36 +57,7 @@ class pybind11_static_property(property): By default, Python replaces the `static_property` itself, but for wrapped C++ types we need to call `static_property.__set__()` in order to propagate the new value to the underlying C++ data structure. */ -extern "C" inline int pybind11_meta_setattro(PyObject *obj, PyObject *name, PyObject *value) { - // Use `_PyType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw - // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`). - PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name); - - // The following assignment combinations are possible: - // 1. `Type.static_prop = value` --> descr_set: `Type.static_prop.__set__(value)` - // 2. `Type.static_prop = other_static_prop` --> setattro: replace existing `static_prop` - // 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment - auto *const static_prop = (PyObject *) get_internals().static_property_type; - const auto call_descr_set = (descr != nullptr) && (value != nullptr) - && (PyObject_IsInstance(descr, static_prop) != 0) - && (PyObject_IsInstance(value, static_prop) == 0); - if (call_descr_set) { - // Call `static_property.__set__()` instead of replacing the `static_property`. -#if !defined(PYPY_VERSION) - return Py_TYPE(descr)->tp_descr_set(descr, obj, value); -#else - if (PyObject *result = PyObject_CallMethod(descr, "__set__", "OO", obj, value)) { - Py_DECREF(result); - return 0; - } else { - return -1; - } -#endif - } else { - // Replace existing attribute. - return PyType_Type.tp_setattro(obj, name, value); - } -} +extern "C" int pybind11_meta_setattro(PyObject *obj, PyObject *name, PyObject *value); /** * Python 3's PyInstanceMethod_Type hides itself via its tp_descr_get, which prevents aliasing @@ -172,356 +65,64 @@ extern "C" inline int pybind11_meta_setattro(PyObject *obj, PyObject *name, PyOb * when called on a class, or a PyMethod, when called on an instance. Override that behaviour here * to do a special case bypass for PyInstanceMethod_Types. */ -extern "C" inline PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name) { - PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name); - if (descr && PyInstanceMethod_Check(descr)) { - Py_INCREF(descr); - return descr; - } - return PyType_Type.tp_getattro(obj, name); -} +extern "C" PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name); /// metaclass `__call__` function that is used to create all pybind11 objects. -extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs) { - - // use the default metaclass call to create/initialize the object - PyObject *self = PyType_Type.tp_call(type, args, kwargs); - if (self == nullptr) { - return nullptr; - } - - // Ensure that the base __init__ function(s) were called - values_and_holders vhs(self); - for (const auto &vh : vhs) { - if (!vh.holder_constructed() && !vhs.is_redundant_value_and_holder(vh)) { - PyErr_Format(PyExc_TypeError, - "%.200s.__init__() must be called when overriding __init__", - get_fully_qualified_tp_name(vh.type->type).c_str()); - Py_DECREF(self); - return nullptr; - } - } - - return self; -} +extern "C" PyObject *pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs); /// Cleanup the type-info for a pybind11-registered type. -extern "C" inline void pybind11_meta_dealloc(PyObject *obj) { - with_internals_if_internals([obj](internals &internals) { - auto *type = (PyTypeObject *) obj; - - // A pybind11-registered type will: - // 1) be found in internals.registered_types_py - // 2) have exactly one associated `detail::type_info` - auto found_type = internals.registered_types_py.find(type); - if (found_type != internals.registered_types_py.end() && found_type->second.size() == 1 - && found_type->second[0]->type == type) { - - auto *tinfo = found_type->second[0]; - auto tindex = std::type_index(*tinfo->cpptype); - internals.direct_conversions.erase(tindex); - - auto &local_internals = get_local_internals(); - if (tinfo->module_local) { - 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); - for (const std::type_info *alias : tinfo->alias_chain) { - auto num_erased = internals.registered_types_cpp_fast.erase(alias); - (void) num_erased; - assert(num_erased > 0); - } -#endif - } - internals.registered_types_py.erase(tinfo->type); - - // Actually just `std::erase_if`, but that's only available in C++20 - auto &cache = internals.inactive_override_cache; - for (auto it = cache.begin(), last = cache.end(); it != last;) { - if (it->first == (PyObject *) tinfo->type) { - it = cache.erase(it); - } else { - ++it; - } - } - - delete tinfo; - } - }); - - PyType_Type.tp_dealloc(obj); -} +extern "C" void pybind11_meta_dealloc(PyObject *obj); /** This metaclass is assigned by default to all pybind11 types and is required in order for static properties to function correctly. Users may override this using `py::metaclass`. Return value: New reference. */ -inline PyTypeObject *make_default_metaclass() { - constexpr auto *name = "pybind11_type"; - auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); - - /* Danger zone: from now (and until PyType_Ready), make sure to - issue no Python C API calls which could potentially invoke the - garbage collector (the GC will call type_traverse(), which will in - turn find the newly constructed type in an invalid state) */ - auto *heap_type = reinterpret_cast(PyType_Type.tp_alloc(&PyType_Type, 0)); - if (!heap_type) { - pybind11_fail("make_default_metaclass(): error allocating metaclass!"); - } - - heap_type->ht_name = name_obj.inc_ref().ptr(); -#ifdef PYBIND11_BUILTIN_QUALNAME - heap_type->ht_qualname = name_obj.inc_ref().ptr(); -#endif - - auto *type = &heap_type->ht_type; - type->tp_name = name; - type->tp_base = type_incref(&PyType_Type); - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; - - type->tp_call = pybind11_meta_call; - - type->tp_setattro = pybind11_meta_setattro; - type->tp_getattro = pybind11_meta_getattro; - - type->tp_dealloc = pybind11_meta_dealloc; - - if (PyType_Ready(type) < 0) { - pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!"); - } - - setattr(reinterpret_cast(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME)); - PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); - - return type; -} +PyTypeObject *make_default_metaclass(); /// For multiple inheritance types we need to recursively register/deregister base pointers for any /// base classes with pointers that are difference from the instance value pointer so that we can /// correctly recognize an offset base class pointer. This calls a function with any offset base /// ptrs. -inline void traverse_offset_bases(void *valueptr, - const detail::type_info *tinfo, - instance *self, - bool (*f)(void * /*parentptr*/, instance * /*self*/)) { - for (handle h : reinterpret_borrow(tinfo->type->tp_bases)) { - if (auto *parent_tinfo = get_type_info(reinterpret_cast(h.ptr()))) { - for (auto &c : parent_tinfo->implicit_casts) { - if (c.first == tinfo->cpptype) { - auto *parentptr = c.second(valueptr); - if (parentptr != valueptr) { - f(parentptr, self); - } - traverse_offset_bases(parentptr, parent_tinfo, self, f); - break; - } - } - } - } -} +void traverse_offset_bases(void *valueptr, + const detail::type_info *tinfo, + instance *self, + bool (*f)(void * /*parentptr*/, instance * /*self*/)); #ifdef Py_GIL_DISABLED -inline void enable_try_inc_ref(PyObject *obj) { -# if PY_VERSION_HEX >= 0x030E00A4 - PyUnstable_EnableTryIncRef(obj); -# else - if (_Py_IsImmortal(obj)) { - return; - } - for (;;) { - Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); - if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) { - // Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states. - return; - } - if (_Py_atomic_compare_exchange_ssize( - &obj->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) { - return; - } - } -# endif -} +void enable_try_inc_ref(PyObject *obj); #endif -inline bool register_instance_impl(void *ptr, instance *self) { - assert(ptr); -#ifdef Py_GIL_DISABLED - enable_try_inc_ref(reinterpret_cast(self)); -#endif - with_instance_map(ptr, [&](instance_map &instances) { instances.emplace(ptr, self); }); - return true; // unused, but gives the same signature as the deregister func -} -inline bool deregister_instance_impl(void *ptr, instance *self) { - assert(ptr); - return with_instance_map(ptr, [&](instance_map &instances) { - auto range = instances.equal_range(ptr); - for (auto it = range.first; it != range.second; ++it) { - if (self == it->second) { - instances.erase(it); - return true; - } - } - return false; - }); -} - -inline void register_instance(instance *self, void *valptr, const type_info *tinfo) { - register_instance_impl(valptr, self); - if (!tinfo->simple_ancestors) { - traverse_offset_bases(valptr, tinfo, self, register_instance_impl); - } -} - -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo) { - bool ret = deregister_instance_impl(valptr, self); - if (!tinfo->simple_ancestors) { - traverse_offset_bases(valptr, tinfo, self, deregister_instance_impl); - } - return ret; -} +bool register_instance_impl(void *ptr, instance *self); +bool deregister_instance_impl(void *ptr, instance *self); + +void register_instance(instance *self, void *valptr, const type_info *tinfo); + +bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); /// Instance creation function for all pybind11 types. It allocates the internal instance layout /// for holding C++ objects and holders. Allocation is done lazily (the first time the instance is /// cast to a reference or pointer), and initialization is done by an `__init__` function. -inline PyObject *make_new_instance(PyTypeObject *type) { -#if defined(PYPY_VERSION) - // PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first - // inherited object is a plain Python type (i.e. not derived from an extension type). Fix it. - ssize_t instance_size = static_cast(sizeof(instance)); - if (type->tp_basicsize < instance_size) { - type->tp_basicsize = instance_size; - } -#endif - PyObject *self = type->tp_alloc(type, 0); - auto *inst = reinterpret_cast(self); - // Allocate the value/holder internals: - inst->allocate_layout(); - - return self; -} +PyObject *make_new_instance(PyTypeObject *type); /// Instance creation function for all pybind11 types. It only allocates space for the /// C++ object, but doesn't call the constructor -- an `__init__` function must do that. -extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) { - return make_new_instance(type); -} +extern "C" PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); /// An `__init__` function constructs the C++ object. Users should provide at least one /// of these using `py::init` or directly with `.def(__init__, ...)`. Otherwise, the /// following default function will be used which simply throws an exception. -extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) { - PyTypeObject *type = Py_TYPE(self); - std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!"; - set_error(PyExc_TypeError, msg.c_str()); - return -1; -} - -inline void add_patient(PyObject *nurse, PyObject *patient) { - auto *instance = reinterpret_cast(nurse); - instance->has_patients = true; - Py_INCREF(patient); - - with_internals([&](internals &internals) { internals.patients[nurse].push_back(patient); }); -} - -inline void clear_patients(PyObject *self) { - auto *instance = reinterpret_cast(self); - std::vector patients; - - with_internals([&](internals &internals) { - auto pos = internals.patients.find(self); - - if (pos == internals.patients.end()) { - pybind11_fail( - "FATAL: Internal consistency check failed: Invalid clear_patients() call."); - } - - // Clearing the patients can cause more Python code to run, which - // can invalidate the iterator. Extract the vector of patients - // from the unordered_map first. - patients = std::move(pos->second); - internals.patients.erase(pos); - }); - - instance->has_patients = false; - for (PyObject *&patient : patients) { - Py_CLEAR(patient); - } -} +extern "C" int pybind11_object_init(PyObject *self, PyObject *, PyObject *); + +void add_patient(PyObject *nurse, PyObject *patient); + +void clear_patients(PyObject *self); /// Clears all internal data from the instance and removes it from registered instances in /// preparation for deallocation. -inline void clear_instance(PyObject *self) { - auto *instance = reinterpret_cast(self); - - // Deallocate any values/holders, if present: - for (auto &v_h : values_and_holders(instance)) { - if (v_h) { - - // We have to deregister before we call dealloc because, for virtual MI types, we still - // need to be able to get the parent pointers. - if (v_h.instance_registered() - && !deregister_instance(instance, v_h.value_ptr(), v_h.type)) { - pybind11_fail( - "pybind11_object_dealloc(): Tried to deallocate unregistered instance!"); - } - - if (instance->owned || v_h.holder_constructed()) { - v_h.type->dealloc(v_h); - } - } else if (v_h.holder_constructed()) { - v_h.type->dealloc(v_h); // Disowned instance. - } - } - // Deallocate the value/holder layout internals: - instance->deallocate_layout(); - - if (instance->weakrefs) { - PyObject_ClearWeakRefs(self); - } - - PyObject **dict_ptr = _PyObject_GetDictPtr(self); - if (dict_ptr) { - Py_CLEAR(*dict_ptr); - } - - if (instance->has_patients) { - clear_patients(self); - } -} +void clear_instance(PyObject *self); /// Instance destructor function for all pybind11 types. It calls `type_info.dealloc` /// to destroy the C++ object itself, while the rest is Python bookkeeping. -extern "C" inline void pybind11_object_dealloc(PyObject *self) { - auto *type = Py_TYPE(self); - - // If this is a GC tracked object, untrack it first - // Note that the track call is implicitly done by the - // default tp_alloc, which we never override. - if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC) != 0) { - PyObject_GC_UnTrack(self); - } - -#if PY_VERSION_HEX >= 0x030D0000 - // PyObject_ClearManagedDict() is available from Python 3.13+. It must be - // called before tp_free() because on Python 3.14+ tp_free no longer - // implicitly clears the managed dict, which would abandon the refcounts of - // objects stored in __dict__ of py::dynamic_attr() types, causing permanent - // memory leaks. - if (PyType_HasFeature(type, Py_TPFLAGS_MANAGED_DICT)) { - PyObject_ClearManagedDict(self); - } -#endif - - clear_instance(self); - - type->tp_free(self); - - // This was not needed before Python 3.8 (Python issue 35810) - // https://github.com/pybind/pybind11/issues/1946 - Py_DECREF(type); -} +extern "C" void pybind11_object_dealloc(PyObject *self); PYBIND11_WARNING_PUSH PYBIND11_WARNING_DISABLE_GCC("-Wredundant-decls") @@ -533,316 +134,33 @@ PYBIND11_WARNING_POP /** Create the type which can be used as a common base for all classes. This is needed in order to satisfy Python's requirements for multiple inheritance. Return value: New reference. */ -inline PyObject *make_object_base_type(PyTypeObject *metaclass) { - constexpr auto *name = "pybind11_object"; - auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); - - /* Danger zone: from now (and until PyType_Ready), make sure to - issue no Python C API calls which could potentially invoke the - garbage collector (the GC will call type_traverse(), which will in - turn find the newly constructed type in an invalid state) */ - auto *heap_type = reinterpret_cast(metaclass->tp_alloc(metaclass, 0)); - if (!heap_type) { - pybind11_fail("make_object_base_type(): error allocating type!"); - } - - heap_type->ht_name = name_obj.inc_ref().ptr(); -#ifdef PYBIND11_BUILTIN_QUALNAME - heap_type->ht_qualname = name_obj.inc_ref().ptr(); -#endif - - auto *type = &heap_type->ht_type; - type->tp_name = name; - type->tp_base = type_incref(&PyBaseObject_Type); - type->tp_basicsize = static_cast(sizeof(instance)); - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; - - type->tp_new = pybind11_object_new; - type->tp_init = pybind11_object_init; - type->tp_dealloc = pybind11_object_dealloc; - - /* Support weak references (needed for the keep_alive feature) */ - type->tp_weaklistoffset = offsetof(instance, weakrefs); - - if (PyType_Ready(type) < 0) { - pybind11_fail("PyType_Ready failed in make_object_base_type(): " + error_string()); - } - - setattr(reinterpret_cast(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME)); - PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); - - assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)); - return reinterpret_cast(heap_type); -} +PyObject *make_object_base_type(PyTypeObject *metaclass); /// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`. -extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { -#if PY_VERSION_HEX >= 0x030D0000 - int ret = PyObject_VisitManagedDict(self, visit, arg); - if (ret) { - return ret; - } -#else - PyObject *&dict = *_PyObject_GetDictPtr(self); - Py_VISIT(dict); -#endif - // https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse - Py_VISIT(Py_TYPE(self)); - return 0; -} +extern "C" int pybind11_traverse(PyObject *self, visitproc visit, void *arg); /// dynamic_attr: Allow the GC to clear the dictionary. -extern "C" inline int pybind11_clear(PyObject *self) { -#if PY_VERSION_HEX >= 0x030D0000 - PyObject_ClearManagedDict(self); -#else - PyObject *&dict = *_PyObject_GetDictPtr(self); - Py_CLEAR(dict); -#endif - return 0; -} +extern "C" int pybind11_clear(PyObject *self); /// Give instances of this type a `__dict__` and opt into garbage collection. -inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) { - auto *type = &heap_type->ht_type; - type->tp_flags |= Py_TPFLAGS_HAVE_GC; -#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET - type->tp_dictoffset = type->tp_basicsize; // place dict at the end - type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it -#else - type->tp_flags |= Py_TPFLAGS_MANAGED_DICT; -#endif - type->tp_traverse = pybind11_traverse; - type->tp_clear = pybind11_clear; - - static PyGetSetDef getset[] - = {{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, nullptr, nullptr}, - {nullptr, nullptr, nullptr, nullptr, nullptr}}; - type->tp_getset = getset; -} +void enable_dynamic_attributes(PyHeapTypeObject *heap_type); /// buffer_protocol: Fill in the view as specified by flags. -extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int flags) { - // Look for a `get_buffer` implementation in this type's info or any bases (following MRO). - type_info *tinfo = nullptr; - for (auto type : reinterpret_borrow(Py_TYPE(obj)->tp_mro)) { - tinfo = get_type_info((PyTypeObject *) type.ptr()); - if (tinfo && tinfo->get_buffer) { - break; - } - } - if (view == nullptr || !tinfo || !tinfo->get_buffer) { - if (view) { - view->obj = nullptr; - } - set_error(PyExc_BufferError, "pybind11_getbuffer(): Internal error"); - return -1; - } - std::memset(view, 0, sizeof(Py_buffer)); - std::unique_ptr info = nullptr; - try { - info.reset(tinfo->get_buffer(obj, tinfo->get_buffer_data)); - } catch (...) { - try_translate_exceptions(); - raise_from(PyExc_BufferError, "Error getting buffer"); - return -1; - } - if (info == nullptr) { - pybind11_fail("FATAL UNEXPECTED SITUATION: tinfo->get_buffer() returned nullptr."); - } - - if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) { - // view->obj = nullptr; // Was just memset to 0, so not necessary - set_error(PyExc_BufferError, "Writable buffer requested for readonly storage"); - return -1; - } - - // Fill in all the information, and then downgrade as requested by the caller, or raise an - // error if that's not possible. - view->itemsize = info->itemsize; - view->len = view->itemsize; - for (auto s : info->shape) { - view->len *= s; - } - view->ndim = static_cast(info->ndim); - view->shape = info->shape.data(); - view->strides = info->strides.data(); - view->readonly = static_cast(info->readonly); - if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) { - view->format = const_cast(info->format.c_str()); - } - - // Note, all contiguity flags imply PyBUF_STRIDES and lower. - if ((flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) { - if (PyBuffer_IsContiguous(view, 'C') == 0) { - std::memset(view, 0, sizeof(Py_buffer)); - set_error(PyExc_BufferError, - "C-contiguous buffer requested for discontiguous storage"); - return -1; - } - } else if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) { - if (PyBuffer_IsContiguous(view, 'F') == 0) { - std::memset(view, 0, sizeof(Py_buffer)); - set_error(PyExc_BufferError, - "Fortran-contiguous buffer requested for discontiguous storage"); - return -1; - } - } else if ((flags & PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS) { - if (PyBuffer_IsContiguous(view, 'A') == 0) { - std::memset(view, 0, sizeof(Py_buffer)); - set_error(PyExc_BufferError, "Contiguous buffer requested for discontiguous storage"); - return -1; - } - - } else if ((flags & PyBUF_STRIDES) != PyBUF_STRIDES) { - // If no strides are requested, the buffer must be C-contiguous. - // https://docs.python.org/3/c-api/buffer.html#contiguity-requests - if (PyBuffer_IsContiguous(view, 'C') == 0) { - std::memset(view, 0, sizeof(Py_buffer)); - set_error(PyExc_BufferError, - "C-contiguous buffer requested for discontiguous storage"); - return -1; - } - - view->strides = nullptr; - - // Since this is a contiguous buffer, it can also pretend to be 1D. - if ((flags & PyBUF_ND) != PyBUF_ND) { - view->shape = nullptr; - view->ndim = 0; - } - } - - // Set these after all checks so they don't leak out into the caller, and can be automatically - // cleaned up on error. - view->buf = info->ptr; - view->internal = info.release(); - view->obj = obj; - Py_INCREF(view->obj); - return 0; -} +extern "C" int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int flags); /// buffer_protocol: Release the resources of the buffer. -extern "C" inline void pybind11_releasebuffer(PyObject *, Py_buffer *view) { - delete (buffer_info *) view->internal; -} +extern "C" void pybind11_releasebuffer(PyObject *, Py_buffer *view); /// Give this type a buffer interface. -inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) { - heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer; - - heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer; - heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer; -} +void enable_buffer_protocol(PyHeapTypeObject *heap_type); /** Create a brand new Python type according to the `type_record` specification. Return value: New reference. */ -inline PyObject *make_new_python_type(const type_record &rec) { - auto name = reinterpret_steal(PYBIND11_FROM_STRING(rec.name)); - - auto qualname = name; - if (rec.scope && !PyModule_Check(rec.scope.ptr()) && hasattr(rec.scope, "__qualname__")) { - qualname = reinterpret_steal( - PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr())); - } - - object module_ = get_module_name_if_available(rec.scope); - const auto *full_name = c_str( -#if !defined(PYPY_VERSION) - module_ ? str(module_).cast() + "." + rec.name : -#endif - rec.name); - - char *tp_doc = nullptr; - if (rec.doc && options::show_user_defined_docstrings()) { - /* Allocate memory for docstring (Python will free this later on) */ - size_t size = std::strlen(rec.doc) + 1; -#if PY_VERSION_HEX >= 0x030D0000 - tp_doc = static_cast(PyMem_MALLOC(size)); -#else - tp_doc = (char *) PyObject_MALLOC(size); -#endif - std::memcpy((void *) tp_doc, rec.doc, size); - } - - auto &internals = get_internals(); - auto bases = tuple(rec.bases); - auto *base = (bases.empty()) ? internals.instance_base : bases[0].ptr(); - - /* Danger zone: from now (and until PyType_Ready), make sure to - issue no Python C API calls which could potentially invoke the - garbage collector (the GC will call type_traverse(), which will in - turn find the newly constructed type in an invalid state) */ - auto *metaclass = rec.metaclass.ptr() ? reinterpret_cast(rec.metaclass.ptr()) - : internals.default_metaclass; - - auto *heap_type = reinterpret_cast(metaclass->tp_alloc(metaclass, 0)); - if (!heap_type) { - pybind11_fail(std::string(rec.name) + ": Unable to create type object!"); - } - - heap_type->ht_name = name.release().ptr(); -#ifdef PYBIND11_BUILTIN_QUALNAME - heap_type->ht_qualname = qualname.inc_ref().ptr(); -#endif - - auto *type = &heap_type->ht_type; - type->tp_name = full_name; - type->tp_doc = tp_doc; - type->tp_base = type_incref(reinterpret_cast(base)); - type->tp_basicsize = static_cast(sizeof(instance)); - if (!bases.empty()) { - type->tp_bases = bases.release().ptr(); - } - - /* Don't inherit base __init__ */ - type->tp_init = pybind11_object_init; - - /* Supported protocols */ - type->tp_as_number = &heap_type->as_number; - type->tp_as_sequence = &heap_type->as_sequence; - type->tp_as_mapping = &heap_type->as_mapping; - type->tp_as_async = &heap_type->as_async; - - /* Flags */ - type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE; - if (!rec.is_final) { - type->tp_flags |= Py_TPFLAGS_BASETYPE; - } - - if (rec.dynamic_attr) { - enable_dynamic_attributes(heap_type); - } - - if (rec.buffer_protocol) { - enable_buffer_protocol(heap_type); - } - - if (rec.custom_type_setup_callback) { - rec.custom_type_setup_callback(heap_type); - } - - if (PyType_Ready(type) < 0) { - pybind11_fail(std::string(rec.name) + ": PyType_Ready failed: " + error_string()); - } - - assert(!rec.dynamic_attr || PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)); - - /* Register type with the parent scope */ - if (rec.scope) { - setattr(rec.scope, rec.name, reinterpret_cast(type)); - } else { - Py_INCREF(type); // Keep it alive forever (reference leak) - } - - if (module_) { // Needed by pydoc - setattr(reinterpret_cast(type), "__module__", module_); - } - - PYBIND11_SET_OLDPY_QUALNAME(type, qualname); - - return reinterpret_cast(type); -} +PyObject *make_new_python_type(const type_record &rec); PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_PRECOMPILED +# include "class-inl.h" // IWYU pragma: export +#endif diff --git a/include/pybind11/detail/cpp_conduit.h b/include/pybind11/detail/cpp_conduit.h index 49c199e14f..c9ce32756c 100644 --- a/include/pybind11/detail/cpp_conduit.h +++ b/include/pybind11/detail/cpp_conduit.h @@ -13,7 +13,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) // Forward declaration needed here: Refactoring opportunity. -extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); +extern "C" PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); inline bool type_is_managed_by_our_internals(PyTypeObject *type_obj) { #if defined(PYPY_VERSION) diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 274e794b5f..c4c780d3d3 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -196,9 +196,9 @@ PYBIND11_NAMESPACE_BEGIN(detail) #define PYBIND11_DUMMY_MODULE_NAME "pybind11_builtins" // Forward declarations -inline PyTypeObject *make_static_property_type(); -inline PyTypeObject *make_default_metaclass(); -inline PyObject *make_object_base_type(PyTypeObject *metaclass); +PyTypeObject *make_static_property_type(); +PyTypeObject *make_default_metaclass(); +PyObject *make_object_base_type(PyTypeObject *metaclass); inline void translate_exception(std::exception_ptr p); inline PyThreadState *get_thread_state_unchecked() { diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 161b9884fa..7fd9c449eb 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -626,13 +626,13 @@ struct cast_sources { // Forward declarations void keep_alive_impl(handle nurse, handle patient); -inline PyObject *make_new_instance(PyTypeObject *type); +PyObject *make_new_instance(PyTypeObject *type); PYBIND11_WARNING_PUSH PYBIND11_WARNING_DISABLE_GCC("-Wredundant-decls") // PYBIND11:REMINDER: Needs refactoring of existing pybind11 code. -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); +bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); PYBIND11_WARNING_POP diff --git a/include/pybind11/trampoline_self_life_support.h b/include/pybind11/trampoline_self_life_support.h index cbfec7f974..be08d9491d 100644 --- a/include/pybind11/trampoline_self_life_support.h +++ b/include/pybind11/trampoline_self_life_support.h @@ -12,7 +12,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) // PYBIND11:REMINDER: Needs refactoring of existing pybind11 code. -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); +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: diff --git a/src/class.cpp b/src/class.cpp new file mode 100644 index 0000000000..5418b4e359 --- /dev/null +++ b/src/class.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 751b0b1dbe..2bfe127509 100644 --- a/src/pybind11_combined.cpp +++ b/src/pybind11_combined.cpp @@ -11,6 +11,7 @@ # error "pybind11 library sources must be compiled with PYBIND11_PRECOMPILED defined." #endif +#include #include #include #include diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index b02892a738..840df80ac2 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -82,6 +82,7 @@ detail_headers = { "include/pybind11/detail/argument_vector.h", + "include/pybind11/detail/class-inl.h", "include/pybind11/detail/class.h", "include/pybind11/detail/common.h", "include/pybind11/detail/cpp_conduit.h", @@ -129,6 +130,7 @@ } sdist_src_files = { + "src/class.cpp", "src/internals.cpp", "src/pybind11_combined.cpp", "src/pytypes.cpp", From f8f4a9dfdf28fd99fa1451a82acb29436faf290c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 23:32:46 -0400 Subject: [PATCH 06/14] fix: suppress -Wredundant-decls for class.h declarations Several functions are forward-declared in headers that cannot include class.h; GCC's -Wredundant-decls (used in CI cxx_flags) flags the second declaration. Assisted-by: ClaudeCode:claude-fable-5 --- include/pybind11/detail/class.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 2ecde4fbf4..74e5ef0593 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -27,6 +27,12 @@ PYBIND11_NAMESPACE_BEGIN(detail) setattr((PyObject *) obj, "__qualname__", nameobj) #endif +PYBIND11_WARNING_PUSH +// Several of these functions are forward-declared in other headers (internals.h, +// type_caster_base.h, trampoline_self_life_support.h, cpp_conduit.h), which cannot +// include this file; the declarations here are the canonical set. +PYBIND11_WARNING_DISABLE_GCC("-Wredundant-decls") + std::string get_fully_qualified_tp_name(PyTypeObject *type); PyTypeObject *type_incref(PyTypeObject *type); @@ -124,13 +130,8 @@ void clear_instance(PyObject *self); /// to destroy the C++ object itself, while the rest is Python bookkeeping. extern "C" void pybind11_object_dealloc(PyObject *self); -PYBIND11_WARNING_PUSH -PYBIND11_WARNING_DISABLE_GCC("-Wredundant-decls") - std::string error_string(); -PYBIND11_WARNING_POP - /** Create the type which can be used as a common base for all classes. This is needed in order to satisfy Python's requirements for multiple inheritance. Return value: New reference. */ @@ -158,6 +159,8 @@ void enable_buffer_protocol(PyHeapTypeObject *heap_type); Return value: New reference. */ PyObject *make_new_python_type(const type_record &rec); +PYBIND11_WARNING_POP + PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) From 57b3abcbb79b08b88b85c4a4e24ad323eb7b12d2 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 22:51:35 -0400 Subject: [PATCH 07/14] feat: move type_caster_base.h non-template definitions to type_caster_base-inl.h Moves the free functions (type-info lookup and registration, instance layout, isinstance_generic, cpp_conduit_method, type_info_description), the loader_life_support members (the function-local thread_local stack stays per-module in both modes), and the two heavy type_caster_generic members (the type_info constructor and the main cast overload). Templates, including load_impl<>, stay in the header. Assisted-by: ClaudeCode:claude-fable-5 --- CMakeLists.txt | 1 + .../pybind11/detail/type_caster_base-inl.h | 553 ++++++++++++++++++ include/pybind11/detail/type_caster_base.h | 538 ++--------------- src/pybind11_combined.cpp | 1 + src/type_caster_base.cpp | 10 + tests/extra_python_package/test_files.py | 2 + 6 files changed, 610 insertions(+), 495 deletions(-) create mode 100644 include/pybind11/detail/type_caster_base-inl.h create mode 100644 src/type_caster_base.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3157a57e1d..35b0035267 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,7 @@ set(PYBIND11_HEADERS include/pybind11/detail/native_enum_data.h include/pybind11/detail/pybind11_namespace_macros.h include/pybind11/detail/struct_smart_holder.h + include/pybind11/detail/type_caster_base-inl.h include/pybind11/detail/type_caster_base.h include/pybind11/detail/typeid.h include/pybind11/detail/using_smart_holder.h diff --git a/include/pybind11/detail/type_caster_base-inl.h b/include/pybind11/detail/type_caster_base-inl.h new file mode 100644 index 0000000000..4a9ba0d822 --- /dev/null +++ b/include/pybind11/detail/type_caster_base-inl.h @@ -0,0 +1,553 @@ +/* + pybind11/detail/type_caster_base-inl.h: Out-of-line definitions for type_caster_base.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 type_caster_base.h; when PYBIND11_PRECOMPILED is defined it +// is only compiled into the pybind11 static library (see src/). + +#pragma once + +#include "type_caster_base.h" + +#include +#include +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +PYBIND11_INLINE loader_life_support *&loader_life_support::tls_current_frame() { + static thread_local loader_life_support *frame_ptr = nullptr; + return frame_ptr; +} + +PYBIND11_INLINE loader_life_support::loader_life_support() { + auto &frame = tls_current_frame(); + parent = frame; + frame = this; +} + +PYBIND11_INLINE loader_life_support::~loader_life_support() { + auto &frame = tls_current_frame(); + if (frame != this) { + pybind11_fail("loader_life_support: internal error"); + } + frame = parent; + for (auto *item : keep_alive) { + Py_DECREF(item); + } +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE bool loader_life_support::try_add_patient(handle h) { + loader_life_support *frame = tls_current_frame(); + if (!frame) { + return false; + } + if (frame->keep_alive.insert(h.ptr()).second) { + Py_INCREF(h.ptr()); + } + return true; +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void loader_life_support::add_patient(handle h) { + if (!try_add_patient(h)) { + // NOTE: It would be nice to include the stack frames here, as this indicates + // use of pybind11::cast<> outside the normal call framework, finding such + // a location is challenging. Developers could consider printing out + // stack frame addresses here using something like __builtin_frame_address(0) + throw cast_error("When called outside a bound function, py::cast() cannot " + "do Python -> C++ conversions which require the creation " + "of temporary values"); + } +} + +// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762. +PYBIND11_INLINE void all_type_info_add_base_most_derived_first(std::vector &bases, + type_info *addl_base) { + for (auto it = bases.begin(); it != bases.end(); it++) { + type_info *existing_base = *it; + if (PyType_IsSubtype(addl_base->type, existing_base->type) != 0) { + bases.insert(it, addl_base); + return; + } + } + bases.push_back(addl_base); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void +all_type_info_populate(PyTypeObject *t, std::vector &bases) { + assert(bases.empty()); + std::vector check; + for (handle parent : reinterpret_borrow(t->tp_bases)) { + check.push_back(reinterpret_cast(parent.ptr())); + } + auto const &type_dict = get_internals().registered_types_py; + for (size_t i = 0; i < check.size(); i++) { + auto *type = check[i]; + // Ignore Python2 old-style class super type: + if (!PyType_Check((PyObject *) type)) { + continue; + } + + // Check `type` in the current set of registered python types: + auto it = type_dict.find(type); + if (it != type_dict.end()) { + // We found a cache entry for it, so it's either pybind-registered or has pre-computed + // pybind bases, but we have to make sure we haven't already seen the type(s) before: + // we want to follow Python/virtual C++ rules that there should only be one instance of + // a common base. + for (auto *tinfo : it->second) { + // NB: Could use a second set here, rather than doing a linear search, but since + // having a large number of immediate pybind11-registered types seems fairly + // unlikely, that probably isn't worthwhile. + bool found = false; + for (auto *known : bases) { + if (known == tinfo) { + found = true; + break; + } + } + if (!found) { + all_type_info_add_base_most_derived_first(bases, tinfo); + } + } + } else if (type->tp_bases) { + // It's some python type, so keep follow its bases classes to look for one or more + // registered types + if (i + 1 == check.size()) { + // When we're at the end, we can pop off the current element to avoid growing + // `check` when adding just one base (which is typical--i.e. when there is no + // multiple inheritance) + check.pop_back(); + i--; + } + for (handle parent : reinterpret_borrow(type->tp_bases)) { + check.push_back(reinterpret_cast(parent.ptr())); + } + } + } +} + +PYBIND11_INLINE const std::vector &all_type_info(PyTypeObject *type) { + return all_type_info_get_cache(type).first->second; +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE detail::type_info *get_type_info(PyTypeObject *type) { + const auto &bases = all_type_info(type); + if (bases.empty()) { + return nullptr; + } + if (bases.size() > 1) { + pybind11_fail( + "pybind11::detail::get_type_info: type has multiple pybind11-registered bases"); + } + return bases.front(); +} + +PYBIND11_INLINE detail::type_info *get_local_type_info_lock_held(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; +} + +PYBIND11_INLINE detail::type_info *get_local_type_info(const std::type_info &tp) { + // NB: internals and local_internals share a single mutex + PYBIND11_LOCK_INTERNALS(get_internals()); + return get_local_type_info_lock_held(tp); +} + +PYBIND11_INLINE detail::type_info *get_global_type_info_lock_held(const std::type_info &tp) { + // 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. + detail::type_info *type_info = nullptr; + auto &internals = get_internals(); +#if PYBIND11_INTERNALS_VERSION >= 12 + auto &fast_types = internals.registered_types_cpp_fast; +#endif + auto &types = internals.registered_types_cpp; +#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 + // We found the type in the slow map but not the fast one, so + // some other DSO added it (otherwise it would be in the fast + // map under &tp) and therefore we must be an alias. Record + // that. + it->second->alias_chain.push_front(&tp); + fast_types.emplace(&tp, it->second); +#endif + type_info = it->second; + } + return type_info; +} + +PYBIND11_INLINE detail::type_info *get_global_type_info(const std::type_info &tp) { + PYBIND11_LOCK_INTERNALS(get_internals()); + return get_global_type_info_lock_held(tp); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE detail::type_info *get_type_info(const std::type_info &tp, + bool throw_if_missing) { + PYBIND11_LOCK_INTERNALS(get_internals()); + if (auto *ltype = get_local_type_info_lock_held(tp)) { + return ltype; + } + if (auto *gtype = get_global_type_info_lock_held(tp)) { + return gtype; + } + + if (throw_if_missing) { + std::string tname = tp.name(); + detail::clean_type_id(tname); + pybind11_fail("pybind11::detail::get_type_info: unable to find type info for \"" + + std::move(tname) + '"'); + } + return nullptr; +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_type_handle(const std::type_info &tp, + bool throw_if_missing) { + detail::type_info *type_info = get_type_info(tp, throw_if_missing); + return handle(type_info ? (reinterpret_cast(type_info->type)) : nullptr); +} + +PYBIND11_INLINE bool try_incref(PyObject *obj) { + // Tries to increment the reference count of an object if it's not zero. +#if defined(Py_GIL_DISABLED) && PY_VERSION_HEX >= 0x030E00A4 + return PyUnstable_TryIncRef(obj); +#elif defined(Py_GIL_DISABLED) + // See + // https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761 + uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local); + local += 1; + if (local == 0) { + // immortal + return true; + } + if (_Py_IsOwnedByCurrentThread(obj)) { + _Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local); +# ifdef Py_REF_DEBUG + _Py_INCREF_IncRefTotal(); +# endif + return true; + } + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); + for (;;) { + // If the shared refcount is zero and the object is either merged + // or may not have weak references, then we cannot incref it. + if (shared == 0 || shared == _Py_REF_MERGED) { + return false; + } + + if (_Py_atomic_compare_exchange_ssize( + &obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) { +# ifdef Py_REF_DEBUG + _Py_INCREF_IncRefTotal(); +# endif + return true; + } + } +#else + assert(Py_REFCNT(obj) > 0); + Py_INCREF(obj); + return true; +#endif +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle +find_registered_python_instance(void *src, const detail::type_info *tinfo) { + return with_instance_map(src, [&](instance_map &instances) { + auto it_instances = instances.equal_range(src); + for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { + for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) { + if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) { + auto *wrapper = reinterpret_cast(it_i->second); + if (try_incref(wrapper)) { + return handle(wrapper); + } + } + } + } + return handle(); + }); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE value_and_holder +instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/, + bool throw_if_missing /*= true in common.h*/) { + // Optimize common case: + if (!find_type || Py_TYPE(this) == find_type->type) { + return value_and_holder(this, find_type, 0, 0); + } + + detail::values_and_holders vhs(this); + auto it = vhs.find(find_type); + if (it != vhs.end()) { + return *it; + } + + if (!throw_if_missing) { + return value_and_holder(); + } + +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + pybind11_fail("pybind11::detail::instance::get_value_and_holder: `" + + get_fully_qualified_tp_name(find_type->type) + + "' is not a pybind11 base of the given `" + + get_fully_qualified_tp_name(Py_TYPE(this)) + "' instance"); +#else + pybind11_fail( + "pybind11::detail::instance::get_value_and_holder: " + "type is not a pybind11 base of the given instance " + "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for type details)"); +#endif +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void instance::allocate_layout() { + const auto &tinfo = all_type_info(Py_TYPE(this)); + + const size_t n_types = tinfo.size(); + + if (n_types == 0) { + pybind11_fail( + "instance allocation failed: new instance has no pybind11-registered base types"); + } + + simple_layout + = n_types == 1 && tinfo.front()->holder_size_in_ptrs <= instance_simple_holder_in_ptrs(); + + // Simple path: no python-side multiple inheritance, and a small-enough holder + if (simple_layout) { + simple_value_holder[0] = nullptr; + simple_holder_constructed = false; + simple_instance_registered = false; + } else { // multiple base types or a too-large holder + // Allocate space to hold: [v1*][h1][v2*][h2]...[bb...] where [vN*] is a value pointer, + // [hN] is the (uninitialized) holder instance for value N, and [bb...] is a set of bool + // values that tracks whether each associated holder has been initialized. Each [block] is + // padded, if necessary, to an integer multiple of sizeof(void *). + size_t space = 0; + for (auto *t : tinfo) { + space += 1; // value pointer + space += t->holder_size_in_ptrs; // holder instance + } + size_t flags_at = space; + space += size_in_ptrs(n_types); // status bytes (holder_constructed and + // instance_registered) + + // Allocate space for flags, values, and holders, and initialize it to 0 (flags and values, + // in particular, need to be 0). Use Python's memory allocation + // functions: Python is using pymalloc, which is designed to be + // efficient for small allocations like the one we're doing here; + // for larger allocations they are just wrappers around malloc. + // TODO: is this still true for pure Python 3.6? + nonsimple.values_and_holders = static_cast(PyMem_Calloc(space, sizeof(void *))); + if (!nonsimple.values_and_holders) { + throw std::bad_alloc(); + } + nonsimple.status + = reinterpret_cast(&nonsimple.values_and_holders[flags_at]); + } + owned = true; +} + +// NOLINTNEXTLINE(readability-make-member-function-const) +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void instance::deallocate_layout() { + if (!simple_layout) { + PyMem_Free(reinterpret_cast(nonsimple.values_and_holders)); + } +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE bool isinstance_generic(handle obj, + const std::type_info &tp) { + handle type = detail::get_type_handle(tp, false); + if (!type) { + return false; + } + return isinstance(obj, type); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_object_handle(const void *ptr, + const detail::type_info *type) { + return with_instance_map(ptr, [&](instance_map &instances) { + auto range = instances.equal_range(ptr); + for (auto it = range.first; it != range.second; ++it) { + for (const auto &vh : values_and_holders(it->second)) { + if (vh.type == type) { + return handle(reinterpret_cast(it->second)); + } + } + } + return handle(); + }); +} + +PYBIND11_INLINE object cpp_conduit_method(handle self, + const bytes &pybind11_platform_abi_id, + const capsule &cpp_type_info_capsule, + const bytes &pointer_kind) { +#ifdef PYBIND11_HAS_STRING_VIEW + using cpp_str = std::string_view; +#else + using cpp_str = std::string; +#endif + if (cpp_str(pybind11_platform_abi_id) != PYBIND11_PLATFORM_ABI_ID) { + return none(); + } + if (std::strcmp(cpp_type_info_capsule.name(), typeid(std::type_info).name()) != 0) { + return none(); + } + if (cpp_str(pointer_kind) != "raw_pointer_ephemeral") { + throw std::runtime_error("Invalid pointer_kind: \"" + std::string(pointer_kind) + "\""); + } + const auto *cpp_type_info = cpp_type_info_capsule.get_pointer(); + type_caster_generic caster(*cpp_type_info); + if (!caster.load(self, false)) { + return none(); + } + return capsule(caster.value, cpp_type_info->name()); +} + +PYBIND11_INLINE std::string quote_cpp_type_name(const std::string &cpp_type_name) { + return cpp_type_name; // No-op for now. See PR #4888 +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE std::string +type_info_description(const std::type_info &ti) { + if (auto *type_data = get_type_info(ti)) { + handle th(reinterpret_cast(type_data->type)); + return th.attr("__module__").cast() + '.' + + th.attr("__qualname__").cast(); + } + return quote_cpp_type_name(clean_type_id(ti.name())); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE +type_caster_generic::type_caster_generic(const std::type_info &type_info) + : typeinfo(get_type_info(type_info)), cpptype(&type_info) {} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle +type_caster_generic::cast(const cast_sources &srcs, + return_value_policy policy, + handle parent, + void *(*copy_constructor)(const void *), + void *(*move_constructor)(const void *), + const void *existing_holder) { + if (!srcs.result.tinfo) { + // No pybind11 type info. Raise an exception. + std::string tname = srcs.downcast.cpptype ? srcs.downcast.cpptype->name() + : srcs.original.cpptype ? srcs.original.cpptype->name() + : ""; + detail::clean_type_id(tname); + std::string msg = "Unregistered type : " + tname; + set_error(PyExc_TypeError, msg.c_str()); + return handle(); + } + + void *src = const_cast(srcs.result.cppobj); + if (src == nullptr) { + return none().release(); + } + const type_info *tinfo = srcs.result.tinfo; + + if (handle registered_inst = find_registered_python_instance(src, tinfo)) { + return registered_inst; + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *wrapper = reinterpret_cast(inst.ptr()); + wrapper->owned = false; + void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); + + switch (policy) { + case return_value_policy::automatic: + case return_value_policy::take_ownership: + valueptr = src; + wrapper->owned = true; + break; + + case return_value_policy::automatic_reference: + case return_value_policy::reference: + valueptr = src; + wrapper->owned = false; + break; + + case return_value_policy::copy: + if (copy_constructor) { + valueptr = copy_constructor(src); + } else { +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + std::string type_name(tinfo->cpptype->name()); + detail::clean_type_id(type_name); + throw cast_error("return_value_policy = copy, but type " + type_name + + " is non-copyable!"); +#else + throw cast_error("return_value_policy = copy, but type is " + "non-copyable! (#define PYBIND11_DETAILED_ERROR_MESSAGES or " + "compile in debug mode for details)"); +#endif + } + wrapper->owned = true; + break; + + case return_value_policy::move: + if (move_constructor) { + valueptr = move_constructor(src); + } else if (copy_constructor) { + valueptr = copy_constructor(src); + } else { +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + std::string type_name(tinfo->cpptype->name()); + detail::clean_type_id(type_name); + throw cast_error("return_value_policy = move, but type " + type_name + + " is neither movable nor copyable!"); +#else + throw cast_error("return_value_policy = move, but type is neither " + "movable nor copyable! " + "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in " + "debug mode for details)"); +#endif + } + wrapper->owned = true; + break; + + case return_value_policy::reference_internal: + valueptr = src; + wrapper->owned = false; + keep_alive_impl(inst, parent); + break; + + default: + throw cast_error("unhandled return_value_policy: should not happen!"); + } + + tinfo->init_instance(wrapper, existing_holder); + + return inst.release(); +} + +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 7fd9c449eb..4163673df7 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -52,138 +52,45 @@ class loader_life_support { // saves a significant cost per function call spent in // loader_life_support destruction. // Note for future C++17 simplification: - // inline static thread_local loader_life_support *tls_current_frame = nullptr; - static loader_life_support *&tls_current_frame() { - static thread_local loader_life_support *frame_ptr = nullptr; - return frame_ptr; - } + // inline static thread_local loader_life_support *frame_ptr = nullptr; + // (Keeping the function-local static: its address is the per-module frame stack, so + // it must live in whatever binary each module links, header-only or precompiled.) + static loader_life_support *&tls_current_frame(); loader_life_support *parent = nullptr; std::unordered_set keep_alive; public: /// A new patient frame is created when a function is entered - loader_life_support() { - auto &frame = tls_current_frame(); - parent = frame; - frame = this; - } + loader_life_support(); /// ... and destroyed after it returns - ~loader_life_support() { - auto &frame = tls_current_frame(); - if (frame != this) { - pybind11_fail("loader_life_support: internal error"); - } - frame = parent; - for (auto *item : keep_alive) { - Py_DECREF(item); - } - } + ~loader_life_support(); /// Keep `h` alive until the current patient frame is destroyed, if there is one. /// Returns false when called outside a bound function (no frame). Use this, rather /// than `add_patient`, when failing to register is acceptable because the caller /// owns the source's lifetime outside the call framework (e.g. a view that points /// into an existing Python object, as opposed to a freshly created temporary). - PYBIND11_NOINLINE static bool try_add_patient(handle h) { - loader_life_support *frame = tls_current_frame(); - if (!frame) { - return false; - } - if (frame->keep_alive.insert(h.ptr()).second) { - Py_INCREF(h.ptr()); - } - return true; - } + static bool try_add_patient(handle h); /// This can only be used inside a pybind11-bound function, either by `argument_loader` /// at argument preparation time or by `py::cast()` at execution time. - PYBIND11_NOINLINE static void add_patient(handle h) { - if (!try_add_patient(h)) { - // NOTE: It would be nice to include the stack frames here, as this indicates - // use of pybind11::cast<> outside the normal call framework, finding such - // a location is challenging. Developers could consider printing out - // stack frame addresses here using something like __builtin_frame_address(0) - throw cast_error("When called outside a bound function, py::cast() cannot " - "do Python -> C++ conversions which require the creation " - "of temporary values"); - } - } + static void add_patient(handle h); }; // Gets the cache entry for the given type, creating it if necessary. The return value is the pair // returned by emplace, i.e. an iterator for the entry and a bool set to `true` if the entry was // just created. -inline std::pair +std::pair all_type_info_get_cache(PyTypeObject *type); // Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762. -inline void all_type_info_add_base_most_derived_first(std::vector &bases, - type_info *addl_base) { - for (auto it = bases.begin(); it != bases.end(); it++) { - type_info *existing_base = *it; - if (PyType_IsSubtype(addl_base->type, existing_base->type) != 0) { - bases.insert(it, addl_base); - return; - } - } - bases.push_back(addl_base); -} +void all_type_info_add_base_most_derived_first(std::vector &bases, + type_info *addl_base); // Populates a just-created cache entry. -PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector &bases) { - assert(bases.empty()); - std::vector check; - for (handle parent : reinterpret_borrow(t->tp_bases)) { - check.push_back(reinterpret_cast(parent.ptr())); - } - auto const &type_dict = get_internals().registered_types_py; - for (size_t i = 0; i < check.size(); i++) { - auto *type = check[i]; - // Ignore Python2 old-style class super type: - if (!PyType_Check((PyObject *) type)) { - continue; - } - - // Check `type` in the current set of registered python types: - auto it = type_dict.find(type); - if (it != type_dict.end()) { - // We found a cache entry for it, so it's either pybind-registered or has pre-computed - // pybind bases, but we have to make sure we haven't already seen the type(s) before: - // we want to follow Python/virtual C++ rules that there should only be one instance of - // a common base. - for (auto *tinfo : it->second) { - // NB: Could use a second set here, rather than doing a linear search, but since - // having a large number of immediate pybind11-registered types seems fairly - // unlikely, that probably isn't worthwhile. - bool found = false; - for (auto *known : bases) { - if (known == tinfo) { - found = true; - break; - } - } - if (!found) { - all_type_info_add_base_most_derived_first(bases, tinfo); - } - } - } else if (type->tp_bases) { - // It's some python type, so keep follow its bases classes to look for one or more - // registered types - if (i + 1 == check.size()) { - // When we're at the end, we can pop off the current element to avoid growing - // `check` when adding just one base (which is typical--i.e. when there is no - // multiple inheritance) - check.pop_back(); - i--; - } - for (handle parent : reinterpret_borrow(type->tp_bases)) { - check.push_back(reinterpret_cast(parent.ptr())); - } - } - } -} +void all_type_info_populate(PyTypeObject *t, std::vector &bases); /** * Extracts vector of type_info pointers of pybind-registered roots of the given Python type. Will @@ -195,172 +102,33 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector &all_type_info(PyTypeObject *type) { - return all_type_info_get_cache(type).first->second; -} +const std::vector &all_type_info(PyTypeObject *type); /** * Gets a single pybind11 type info for a python type. Returns nullptr if neither the type nor any * ancestors are pybind11-registered. Throws an exception if there are multiple bases--use * `all_type_info` instead if you want to support multiple bases. */ -PYBIND11_NOINLINE detail::type_info *get_type_info(PyTypeObject *type) { - const auto &bases = all_type_info(type); - if (bases.empty()) { - return nullptr; - } - if (bases.size() > 1) { - pybind11_fail( - "pybind11::detail::get_type_info: type has multiple pybind11-registered bases"); - } - return bases.front(); -} +detail::type_info *get_type_info(PyTypeObject *type); -inline detail::type_info *get_local_type_info_lock_held(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; -} +detail::type_info *get_local_type_info_lock_held(const std::type_info &tp); -inline detail::type_info *get_local_type_info(const std::type_info &tp) { - // NB: internals and local_internals share a single mutex - PYBIND11_LOCK_INTERNALS(get_internals()); - return get_local_type_info_lock_held(tp); -} +detail::type_info *get_local_type_info(const std::type_info &tp); -inline detail::type_info *get_global_type_info_lock_held(const std::type_info &tp) { - // 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. - detail::type_info *type_info = nullptr; - auto &internals = get_internals(); -#if PYBIND11_INTERNALS_VERSION >= 12 - auto &fast_types = internals.registered_types_cpp_fast; -#endif - auto &types = internals.registered_types_cpp; -#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 - // We found the type in the slow map but not the fast one, so - // some other DSO added it (otherwise it would be in the fast - // map under &tp) and therefore we must be an alias. Record - // that. - it->second->alias_chain.push_front(&tp); - fast_types.emplace(&tp, it->second); -#endif - type_info = it->second; - } - return type_info; -} +detail::type_info *get_global_type_info_lock_held(const std::type_info &tp); -inline detail::type_info *get_global_type_info(const std::type_info &tp) { - PYBIND11_LOCK_INTERNALS(get_internals()); - return get_global_type_info_lock_held(tp); -} +detail::type_info *get_global_type_info(const std::type_info &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_info &tp, - bool throw_if_missing = false) { - PYBIND11_LOCK_INTERNALS(get_internals()); - if (auto *ltype = get_local_type_info_lock_held(tp)) { - return ltype; - } - if (auto *gtype = get_global_type_info_lock_held(tp)) { - return gtype; - } +detail::type_info *get_type_info(const std::type_info &tp, bool throw_if_missing = false); - if (throw_if_missing) { - std::string tname = tp.name(); - detail::clean_type_id(tname); - pybind11_fail("pybind11::detail::get_type_info: unable to find type info for \"" - + std::move(tname) + '"'); - } - return nullptr; -} +handle get_type_handle(const std::type_info &tp, bool throw_if_missing); -PYBIND11_NOINLINE handle get_type_handle(const std::type_info &tp, bool throw_if_missing) { - detail::type_info *type_info = get_type_info(tp, throw_if_missing); - return handle(type_info ? (reinterpret_cast(type_info->type)) : nullptr); -} - -inline bool try_incref(PyObject *obj) { - // Tries to increment the reference count of an object if it's not zero. -#if defined(Py_GIL_DISABLED) && PY_VERSION_HEX >= 0x030E00A4 - return PyUnstable_TryIncRef(obj); -#elif defined(Py_GIL_DISABLED) - // See - // https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761 - uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local); - local += 1; - if (local == 0) { - // immortal - return true; - } - if (_Py_IsOwnedByCurrentThread(obj)) { - _Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local); -# ifdef Py_REF_DEBUG - _Py_INCREF_IncRefTotal(); -# endif - return true; - } - Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); - for (;;) { - // If the shared refcount is zero and the object is either merged - // or may not have weak references, then we cannot incref it. - if (shared == 0 || shared == _Py_REF_MERGED) { - return false; - } - - if (_Py_atomic_compare_exchange_ssize( - &obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) { -# ifdef Py_REF_DEBUG - _Py_INCREF_IncRefTotal(); -# endif - return true; - } - } -#else - assert(Py_REFCNT(obj) > 0); - Py_INCREF(obj); - return true; -#endif -} +bool try_incref(PyObject *obj); // Searches the inheritance graph for a registered Python instance, using all_type_info(). -PYBIND11_NOINLINE handle find_registered_python_instance(void *src, - const detail::type_info *tinfo) { - return with_instance_map(src, [&](instance_map &instances) { - auto it_instances = instances.equal_range(src); - for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { - for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) { - if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) { - auto *wrapper = reinterpret_cast(it_i->second); - if (try_incref(wrapper)) { - return handle(wrapper); - } - } - } - } - return handle(); - }); -} +handle find_registered_python_instance(void *src, const detail::type_info *tinfo); // Container for accessing and iterating over an instance's values/holders struct values_and_holders { @@ -448,113 +216,12 @@ struct values_and_holders { * The returned object should be short-lived: in particular, it must not outlive the called-upon * instance. */ -PYBIND11_NOINLINE value_and_holder -instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/, - bool throw_if_missing /*= true in common.h*/) { - // Optimize common case: - if (!find_type || Py_TYPE(this) == find_type->type) { - return value_and_holder(this, find_type, 0, 0); - } +// (get_value_and_holder, allocate_layout, and deallocate_layout are declared inside +// struct instance in detail/common.h; definitions are in type_caster_base-inl.h.) - detail::values_and_holders vhs(this); - auto it = vhs.find(find_type); - if (it != vhs.end()) { - return *it; - } - - if (!throw_if_missing) { - return value_and_holder(); - } - -#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - pybind11_fail("pybind11::detail::instance::get_value_and_holder: `" - + get_fully_qualified_tp_name(find_type->type) - + "' is not a pybind11 base of the given `" - + get_fully_qualified_tp_name(Py_TYPE(this)) + "' instance"); -#else - pybind11_fail( - "pybind11::detail::instance::get_value_and_holder: " - "type is not a pybind11 base of the given instance " - "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for type details)"); -#endif -} - -PYBIND11_NOINLINE void instance::allocate_layout() { - const auto &tinfo = all_type_info(Py_TYPE(this)); - - const size_t n_types = tinfo.size(); - - if (n_types == 0) { - pybind11_fail( - "instance allocation failed: new instance has no pybind11-registered base types"); - } - - simple_layout - = n_types == 1 && tinfo.front()->holder_size_in_ptrs <= instance_simple_holder_in_ptrs(); - - // Simple path: no python-side multiple inheritance, and a small-enough holder - if (simple_layout) { - simple_value_holder[0] = nullptr; - simple_holder_constructed = false; - simple_instance_registered = false; - } else { // multiple base types or a too-large holder - // Allocate space to hold: [v1*][h1][v2*][h2]...[bb...] where [vN*] is a value pointer, - // [hN] is the (uninitialized) holder instance for value N, and [bb...] is a set of bool - // values that tracks whether each associated holder has been initialized. Each [block] is - // padded, if necessary, to an integer multiple of sizeof(void *). - size_t space = 0; - for (auto *t : tinfo) { - space += 1; // value pointer - space += t->holder_size_in_ptrs; // holder instance - } - size_t flags_at = space; - space += size_in_ptrs(n_types); // status bytes (holder_constructed and - // instance_registered) - - // Allocate space for flags, values, and holders, and initialize it to 0 (flags and values, - // in particular, need to be 0). Use Python's memory allocation - // functions: Python is using pymalloc, which is designed to be - // efficient for small allocations like the one we're doing here; - // for larger allocations they are just wrappers around malloc. - // TODO: is this still true for pure Python 3.6? - nonsimple.values_and_holders = static_cast(PyMem_Calloc(space, sizeof(void *))); - if (!nonsimple.values_and_holders) { - throw std::bad_alloc(); - } - nonsimple.status - = reinterpret_cast(&nonsimple.values_and_holders[flags_at]); - } - owned = true; -} +bool isinstance_generic(handle obj, const std::type_info &tp); -// NOLINTNEXTLINE(readability-make-member-function-const) -PYBIND11_NOINLINE void instance::deallocate_layout() { - if (!simple_layout) { - PyMem_Free(reinterpret_cast(nonsimple.values_and_holders)); - } -} - -PYBIND11_NOINLINE bool isinstance_generic(handle obj, const std::type_info &tp) { - handle type = detail::get_type_handle(tp, false); - if (!type) { - return false; - } - return isinstance(obj, type); -} - -PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_info *type) { - return with_instance_map(ptr, [&](instance_map &instances) { - auto range = instances.equal_range(ptr); - for (auto it = range.first; it != range.second; ++it) { - for (const auto &vh : values_and_holders(it->second)) { - if (vh.type == type) { - return handle(reinterpret_cast(it->second)); - } - } - } - return handle(); - }); -} +handle get_object_handle(const void *ptr, const detail::type_info *type); // Information about how type_caster_generic::cast() can obtain its source object struct cast_sources { @@ -996,8 +663,7 @@ 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) - : typeinfo(get_type_info(type_info)), cpptype(&type_info) {} + explicit type_caster_generic(const std::type_info &type_info); explicit type_caster_generic(const type_info *typeinfo) : typeinfo(typeinfo), cpptype(typeinfo ? typeinfo->cpptype : nullptr) {} @@ -1027,104 +693,12 @@ class type_caster_generic { return cast(srcs, policy, parent, nullptr, nullptr, existing_holder); } - PYBIND11_NOINLINE static handle cast(const cast_sources &srcs, - return_value_policy policy, - handle parent, - void *(*copy_constructor)(const void *), - void *(*move_constructor)(const void *), - const void *existing_holder = nullptr) { - if (!srcs.result.tinfo) { - // No pybind11 type info. Raise an exception. - std::string tname = srcs.downcast.cpptype ? srcs.downcast.cpptype->name() - : srcs.original.cpptype ? srcs.original.cpptype->name() - : ""; - detail::clean_type_id(tname); - std::string msg = "Unregistered type : " + tname; - set_error(PyExc_TypeError, msg.c_str()); - return handle(); - } - - void *src = const_cast(srcs.result.cppobj); - if (src == nullptr) { - return none().release(); - } - const type_info *tinfo = srcs.result.tinfo; - - if (handle registered_inst = find_registered_python_instance(src, tinfo)) { - return registered_inst; - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *wrapper = reinterpret_cast(inst.ptr()); - wrapper->owned = false; - void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); - - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::take_ownership: - valueptr = src; - wrapper->owned = true; - break; - - case return_value_policy::automatic_reference: - case return_value_policy::reference: - valueptr = src; - wrapper->owned = false; - break; - - case return_value_policy::copy: - if (copy_constructor) { - valueptr = copy_constructor(src); - } else { -#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - std::string type_name(tinfo->cpptype->name()); - detail::clean_type_id(type_name); - throw cast_error("return_value_policy = copy, but type " + type_name - + " is non-copyable!"); -#else - throw cast_error("return_value_policy = copy, but type is " - "non-copyable! (#define PYBIND11_DETAILED_ERROR_MESSAGES or " - "compile in debug mode for details)"); -#endif - } - wrapper->owned = true; - break; - - case return_value_policy::move: - if (move_constructor) { - valueptr = move_constructor(src); - } else if (copy_constructor) { - valueptr = copy_constructor(src); - } else { -#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - std::string type_name(tinfo->cpptype->name()); - detail::clean_type_id(type_name); - throw cast_error("return_value_policy = move, but type " + type_name - + " is neither movable nor copyable!"); -#else - throw cast_error("return_value_policy = move, but type is neither " - "movable nor copyable! " - "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in " - "debug mode for details)"); -#endif - } - wrapper->owned = true; - break; - - case return_value_policy::reference_internal: - valueptr = src; - wrapper->owned = false; - keep_alive_impl(inst, parent); - break; - - default: - throw cast_error("unhandled return_value_policy: should not happen!"); - } - - tinfo->init_instance(wrapper, existing_holder); - - return inst.release(); - } + static handle cast(const cast_sources &srcs, + return_value_policy policy, + handle parent, + void *(*copy_constructor)(const void *), + void *(*move_constructor)(const void *), + const void *existing_holder = nullptr); // Base methods for generic caster; there are overridden in copyable_holder_caster void load_value(value_and_holder &&v_h) { @@ -1326,31 +900,10 @@ class type_caster_generic { void *value = nullptr; }; -inline object cpp_conduit_method(handle self, - const bytes &pybind11_platform_abi_id, - const capsule &cpp_type_info_capsule, - const bytes &pointer_kind) { -#ifdef PYBIND11_HAS_STRING_VIEW - using cpp_str = std::string_view; -#else - using cpp_str = std::string; -#endif - if (cpp_str(pybind11_platform_abi_id) != PYBIND11_PLATFORM_ABI_ID) { - return none(); - } - if (std::strcmp(cpp_type_info_capsule.name(), typeid(std::type_info).name()) != 0) { - return none(); - } - if (cpp_str(pointer_kind) != "raw_pointer_ephemeral") { - throw std::runtime_error("Invalid pointer_kind: \"" + std::string(pointer_kind) + "\""); - } - const auto *cpp_type_info = cpp_type_info_capsule.get_pointer(); - type_caster_generic caster(*cpp_type_info); - if (!caster.load(self, false)) { - return none(); - } - return capsule(caster.value, cpp_type_info->name()); -} +object cpp_conduit_method(handle self, + const bytes &pybind11_platform_abi_id, + const capsule &cpp_type_info_capsule, + const bytes &pointer_kind); /** * Determine suitable casting operator for pointer-or-lvalue-casting type casters. The type caster @@ -1717,18 +1270,13 @@ class type_caster_base : public type_caster_generic { static Constructor make_move_constructor(...) { return nullptr; } }; -inline std::string quote_cpp_type_name(const std::string &cpp_type_name) { - return cpp_type_name; // No-op for now. See PR #4888 -} +std::string quote_cpp_type_name(const std::string &cpp_type_name); -PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) { - if (auto *type_data = get_type_info(ti)) { - handle th(reinterpret_cast(type_data->type)); - return th.attr("__module__").cast() + '.' - + th.attr("__qualname__").cast(); - } - return quote_cpp_type_name(clean_type_id(ti.name())); -} +std::string type_info_description(const std::type_info &ti); PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_PRECOMPILED +# include "type_caster_base-inl.h" // IWYU pragma: export +#endif diff --git a/src/pybind11_combined.cpp b/src/pybind11_combined.cpp index 2bfe127509..718ddc8291 100644 --- a/src/pybind11_combined.cpp +++ b/src/pybind11_combined.cpp @@ -13,5 +13,6 @@ #include #include +#include #include #include diff --git a/src/type_caster_base.cpp b/src/type_caster_base.cpp new file mode 100644 index 0000000000..f9119044dc --- /dev/null +++ b/src/type_caster_base.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/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 840df80ac2..79af6f679e 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -97,6 +97,7 @@ "include/pybind11/detail/native_enum_data.h", "include/pybind11/detail/pybind11_namespace_macros.h", "include/pybind11/detail/struct_smart_holder.h", + "include/pybind11/detail/type_caster_base-inl.h", "include/pybind11/detail/type_caster_base.h", "include/pybind11/detail/typeid.h", "include/pybind11/detail/using_smart_holder.h", @@ -132,6 +133,7 @@ sdist_src_files = { "src/class.cpp", "src/internals.cpp", + "src/type_caster_base.cpp", "src/pybind11_combined.cpp", "src/pytypes.cpp", } From d495d3e047126ba2d5df7ced028cd5a444e8cb2d Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 7 Aug 2026 08:36:59 -0400 Subject: [PATCH 08/14] fix: make gil.h self-contained for PyPy/GraalPy and silence -Wredundant-decls gil.h evaluated PYBIND11_SIMPLE_GIL_MANAGEMENT before including common.h, which defines it on PyPy/GraalPy. Every existing TU included common.h first through pybind11.h, so this only surfaced when src/type_caster_base.cpp reached gil.h directly. Also suppress GCC -Wredundant-decls for the isinstance_generic declaration duplicated in pytypes.h. Assisted-by: ClaudeCode:claude-fable-5 --- include/pybind11/detail/type_caster_base.h | 4 ++++ include/pybind11/gil.h | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 4163673df7..095475974e 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -219,7 +219,11 @@ struct values_and_holders { // (get_value_and_holder, allocate_layout, and deallocate_layout are declared inside // struct instance in detail/common.h; definitions are in type_caster_base-inl.h.) +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_GCC("-Wredundant-decls") +// also forward-declared in pytypes.h bool isinstance_generic(handle obj, const std::type_info &tp); +PYBIND11_WARNING_POP handle get_object_handle(const void *ptr, const detail::type_info *type); diff --git a/include/pybind11/gil.h b/include/pybind11/gil.h index 9e799b3cf7..e43d4cb2e9 100644 --- a/include/pybind11/gil.h +++ b/include/pybind11/gil.h @@ -9,9 +9,12 @@ #pragma once +// common.h must come first: on PyPy/GraalPy it defines PYBIND11_SIMPLE_GIL_MANAGEMENT, +// which selects the branch below. +#include "detail/common.h" + #if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) -# include "detail/common.h" # include "gil_simple.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -23,7 +26,6 @@ PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) #else -# include "detail/common.h" # include "detail/internals.h" # include From a70e3e1ba29acbda4fb5e1ac3d7ae9ffc4475b27 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 23:11:27 -0400 Subject: [PATCH 09/14] 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 | 73 ++++++ .../pybind11/detail/exception_translation.h | 52 +--- include/pybind11/detail/internals-inl.h | 231 ++++++++++++++++++ include/pybind11/detail/internals.h | 230 ++--------------- 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, 394 insertions(+), 265 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 efceeec689..e41f882326 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -1161,14 +1161,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 {}; @@ -1429,3 +1423,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..de14461f2c --- /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 &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..ea1998435a 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(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; + } +} + +// 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 &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 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 c4c780d3d3..b3042741a4 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -199,7 +199,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) @@ -216,22 +216,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 @@ -339,19 +324,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; @@ -530,10 +503,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> @@ -555,88 +525,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`. @@ -897,109 +791,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) @@ -1038,6 +857,8 @@ inline auto with_exception_translators(const F &cb) 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 @@ 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 dc0482e38da3edba76191f26c0c7534ff863f35d Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 7 Aug 2026 12:25:25 -0400 Subject: [PATCH 10/14] 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 b3042741a4..3d48c5fdf0 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -527,7 +527,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); From 15a37218404ff11648e8f720b080c1f70b983db1 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 23:17:12 -0400 Subject: [PATCH 11/14] feat: move pybind11.h non-template definitions to pybind11-inl.h The headline of the split: cpp_function's make_function_record, initialize_generic, destruct, and the 440-line dispatcher move out of line, along with generic_type::initialize, enum_base, the function signature generators, module cache helpers, keep_alive_impl, get_type_override, error_already_set::what, and detail::print. The templated initialize(), descr.h machinery, and the module/class_ API stay in the header. Assisted-by: ClaudeCode:claude-fable-5 --- CMakeLists.txt | 1 + include/pybind11/pybind11-inl.h | 1493 ++++++++++++++++++++++ include/pybind11/pybind11.h | 1461 +-------------------- src/pybind11.cpp | 10 + src/pybind11_combined.cpp | 18 +- tests/extra_python_package/test_files.py | 2 + 6 files changed, 1552 insertions(+), 1433 deletions(-) create mode 100644 include/pybind11/pybind11-inl.h create mode 100644 src/pybind11.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 906aac2c39..ea1b3a157b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,6 +238,7 @@ set(PYBIND11_HEADERS include/pybind11/native_enum.h include/pybind11/numpy.h include/pybind11/operators.h + include/pybind11/pybind11-inl.h include/pybind11/pybind11.h include/pybind11/pytypes-inl.h include/pybind11/pytypes.h diff --git a/include/pybind11/pybind11-inl.h b/include/pybind11/pybind11-inl.h new file mode 100644 index 0000000000..29abe5377a --- /dev/null +++ b/include/pybind11/pybind11-inl.h @@ -0,0 +1,1493 @@ +/* + pybind11/pybind11-inl.h: Out-of-line definitions for pybind11.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 pybind11.h; when PYBIND11_PRECOMPILED is defined it is only +// compiled into the pybind11 static library (see src/). + +#pragma once + +#include "pybind11.h" + +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_INLINE std::string replace_newlines_and_squash(const char *text) { + const char *whitespaces = " \t\n\r\f\v"; + std::string result(text); + bool previous_is_whitespace = false; + + if (result.size() >= 2) { + // Do not modify string representations + char first_char = result[0]; + char last_char = result[result.size() - 1]; + if (first_char == last_char && first_char == '\'') { + return result; + } + } + result.clear(); + + // Replace characters in whitespaces array with spaces and squash consecutive spaces + while (*text != '\0') { + if (std::strchr(whitespaces, *text)) { + if (!previous_is_whitespace) { + result += ' '; + previous_is_whitespace = true; + } + } else { + result += *text; + previous_is_whitespace = false; + } + ++text; + } + + // Strip leading and trailing whitespaces + const size_t str_begin = result.find_first_not_of(whitespaces); + if (str_begin == std::string::npos) { + return ""; + } + + const size_t str_end = result.find_last_not_of(whitespaces); + const size_t str_range = str_end - str_begin + 1; + + return result.substr(str_begin, str_range); +} + +PYBIND11_INLINE std::string generate_function_signature(const char *type_caster_name_field, + detail::function_record *func_rec, + const std::type_info *const *types, + size_t &type_index, + size_t &arg_index) { + std::string signature; + bool is_starred = false; + // `is_return_value.top()` is true if we are currently inside the return type of the + // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops + // back to the previous state. + std::stack is_return_value({false}); + // The following characters have special meaning in the signature parsing. Literals + // containing these are escaped with `!`. + std::string special_chars("!@%{}-"); + for (const auto *pc = type_caster_name_field; *pc != '\0'; ++pc) { + const auto c = *pc; + if (c == '{') { + // Write arg name for everything except *args and **kwargs. + // Detect {@*args...} or {@**kwargs...} + is_starred = *(pc + 1) == '@' && *(pc + 2) == '*'; + if (is_starred) { + continue; + } + // Separator for keyword-only arguments, placed before the kw + // arguments start (unless we are already putting an *args) + if (!func_rec->has_args && arg_index == func_rec->nargs_pos) { + signature += "*, "; + } + if (arg_index < func_rec->args.size() && func_rec->args[arg_index].name) { + signature += func_rec->args[arg_index].name; + } else if (arg_index == 0 && func_rec->is_method) { + signature += "self"; + } else { + signature += "arg" + std::to_string(arg_index - (func_rec->is_method ? 1 : 0)); + } + signature += ": "; + } else if (c == '}') { + // Write default value if available. + if (!is_starred && arg_index < func_rec->args.size() + && func_rec->args[arg_index].descr) { + signature += " = "; + signature += detail::replace_newlines_and_squash(func_rec->args[arg_index].descr); + } + // Separator for positional-only arguments (placed after the + // argument, rather than before like * + if (func_rec->nargs_pos_only > 0 && (arg_index + 1) == func_rec->nargs_pos_only) { + signature += ", /"; + } + if (!is_starred) { + arg_index++; + } + } else if (c == '%') { + const std::type_info *t = types[type_index++]; + if (!t) { + pybind11_fail("Internal error while parsing type signature (1)"); + } + if (auto *tinfo = detail::get_type_info(*t)) { + handle th(reinterpret_cast(tinfo->type)); + signature += th.attr("__module__").cast() + "." + + th.attr("__qualname__").cast(); + } else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) { + signature += th.attr("__module__").cast() + "." + + th.attr("__qualname__").cast(); + } else if (func_rec->is_new_style_constructor && arg_index == 0) { + // A new-style `__init__` takes `self` as `value_and_holder`. + // Rewrite it to the proper class type. + signature += func_rec->scope.attr("__module__").cast() + "." + + func_rec->scope.attr("__qualname__").cast(); + } else { + signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name())); + } + } else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { + // typing::Literal escapes special characters with ! + signature += *++pc; + } else if (c == '@') { + // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see + // typing::Callable/detail::arg_descr/detail::return_descr). + // `@~ ... @!` inverts the current context (see detail::inv_descr). + if (*(pc + 1) == '^') { + is_return_value.emplace(false); + ++pc; + continue; + } + if (*(pc + 1) == '$') { + is_return_value.emplace(true); + ++pc; + continue; + } + if (*(pc + 1) == '~') { + is_return_value.emplace(!is_return_value.top()); + ++pc; + continue; + } + if (*(pc + 1) == '!') { + is_return_value.pop(); + ++pc; + continue; + } + // Handle types that differ depending on whether they appear + // in an argument or a return value position (see io_name). + // For named arguments (py::arg()) with noconvert set, return value type is used. + ++pc; + if (!is_return_value.top() + && (!(arg_index < func_rec->args.size() && !func_rec->args[arg_index].convert))) { + while (*pc != '\0' && *pc != '@') { + signature += *pc++; + } + if (*pc == '@') { + ++pc; + } + while (*pc != '\0' && *pc != '@') { + ++pc; + } + } else { + while (*pc != '\0' && *pc != '@') { + ++pc; + } + if (*pc == '@') { + ++pc; + } + while (*pc != '\0' && *pc != '@') { + signature += *pc++; + } + } + } else { + if (c == '-' && *(pc + 1) == '>') { + is_return_value.emplace(true); + } + signature += c; + } + } + return signature; +} + +PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods) +PYBIND11_INLINE void tp_dealloc_impl(PyObject *self) { + // Save type before PyObject_Free invalidates self. + auto *type = Py_TYPE(self); + auto *py_func_rec = reinterpret_cast(self); + cpp_function::destruct(py_func_rec->cpp_func_rec); + py_func_rec->cpp_func_rec = nullptr; + // PyObject_New increments the heap type refcount and allocates via + // PyObject_Malloc; balance both here + PyObject_Free(self); + Py_DECREF(type); +} + +PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods) +PYBIND11_INLINE PyObject *get_cached_module(pybind11::str const &nameobj) { + dict state = detail::get_python_state_dict(); + if (!state.contains("__pybind11_module_cache")) { + return nullptr; + } + dict cache = state["__pybind11_module_cache"]; + if (!cache.contains(nameobj)) { + return nullptr; + } + return cache[nameobj].ptr(); +} + +PYBIND11_INLINE void cache_completed_module(pybind11::object const &mod) { + dict state = detail::get_python_state_dict(); + if (!state.contains("__pybind11_module_cache")) { + state["__pybind11_module_cache"] = dict(); + } + state["__pybind11_module_cache"][mod.attr("__spec__").attr("name")] = mod; +} + +PYBIND11_INLINE PyObject *cached_create_module(PyObject *spec, PyModuleDef *) { + (void) &cache_completed_module; // silence unused-function warnings, it is used in a macro + + auto nameobj = getattr(reinterpret_borrow(spec), "name", none()); + if (nameobj.is_none()) { + set_error(PyExc_ImportError, "module spec is missing a name"); + return nullptr; + } + + auto *mod = get_cached_module(nameobj); + if (mod) { + Py_INCREF(mod); + } else { + mod = PyModule_NewObject(nameobj.ptr()); + } + return mod; +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_INLINE dict globals() { +#if PY_VERSION_HEX >= 0x030d0000 + PyObject *p = PyEval_GetFrameGlobals(); + return p ? reinterpret_steal(p) + : reinterpret_borrow(module_::import("__main__").attr("__dict__").ptr()); +#else + PyObject *p = PyEval_GetGlobals(); + return reinterpret_borrow(p ? p : module_::import("__main__").attr("__dict__").ptr()); +#endif +} + +PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_INLINE void call_operator_delete(void *p, size_t s, size_t a) { + (void) s; + (void) a; +#if defined(__cpp_aligned_new) + if (a > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { +# ifdef __cpp_sized_deallocation + ::operator delete(p, s, std::align_val_t(a)); +# else + ::operator delete(p, std::align_val_t(a)); +# endif + return; + } +#endif +#ifdef __cpp_sized_deallocation + ::operator delete(p, s); +#else + ::operator delete(p); +#endif +} + +PYBIND11_INLINE void add_class_method(object &cls, const char *name_, const cpp_function &cf) { + cls.attr(cf.name()) = cf; + if (std::strcmp(name_, "__eq__") == 0 && !cls.attr("__dict__").contains("__hash__")) { + cls.attr("__hash__") = none(); + } +} + +PYBIND11_INLINE str enum_name(handle arg) { + dict entries = type::handle_of(arg).attr("__entries"); + for (auto kv : entries) { + if (handle(kv.second[int_(0)]).equal(arg)) { + return pybind11::str(kv.first); + } + } + return "???"; +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void keep_alive_impl(handle nurse, handle patient) { + if (!nurse || !patient) { + pybind11_fail("Could not activate keep_alive!"); + } + + if (patient.is_none() || nurse.is_none()) { + return; /* Nothing to keep alive or nothing to be kept alive by */ + } + + auto tinfo = all_type_info(Py_TYPE(nurse.ptr())); + if (!tinfo.empty()) { + /* It's a pybind-registered type, so we can store the patient in the + * internal list. */ + add_patient(nurse.ptr(), patient.ptr()); + } else { + /* Fall back to clever approach based on weak references taken from + * Boost.Python. This is not used for pybind-registered types because + * the objects can be destroyed out-of-order in a GC pass. */ + cpp_function disable_lifesupport([patient](handle weakref) { + patient.dec_ref(); + weakref.dec_ref(); + }); + + weakref wr(nurse, disable_lifesupport); + + patient.inc_ref(); /* reference patient and leak the weak reference */ + (void) wr.release(); + } +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void +keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret) { + auto get_arg = [&](size_t n) { + if (n == 0) { + return ret; + } + if (n == 1 && call.init_self) { + return call.init_self; + } + if (n <= call.args.size()) { + return call.args[n - 1]; + } + return handle(); + }; + + keep_alive_impl(get_arg(Nurse), get_arg(Patient)); +} + +PYBIND11_INLINE std::pair +all_type_info_get_cache(PyTypeObject *type) { + auto res = with_internals([type](internals &internals) { + auto ins = internals + .registered_types_py +#ifdef __cpp_lib_unordered_map_try_emplace + .try_emplace(type); +#else + .emplace(type, std::vector()); +#endif + if (ins.second) { + // For free-threading mode, this call must be under + // the with_internals() mutex lock, to avoid that other threads + // continue running with the empty ins.first->second. + all_type_info_populate(type, ins.first->second); + } + return ins; + }); + if (res.second) { + // New cache entry created; set up a weak reference to automatically remove it if the type + // gets destroyed: + weakref(reinterpret_cast(type), cpp_function([type](handle wr) { + with_internals([type](internals &internals) { + internals.registered_types_py.erase(type); + + // TODO consolidate the erasure code in pybind11_meta_dealloc() in class.h + auto &cache = internals.inactive_override_cache; + for (auto it = cache.begin(), last = cache.end(); it != last;) { + if (it->first == reinterpret_cast(type)) { + it = cache.erase(it); + } else { + ++it; + } + } + }); + + wr.dec_ref(); + })) + .release(); + } + + return res; +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_INLINE void register_exception_translator(ExceptionTranslator &&translator) { + detail::with_exception_translators( + [&](std::forward_list &exception_translators, + std::forward_list &local_exception_translators) { + (void) local_exception_translators; + exception_translators.push_front(std::forward(translator)); + }); +} + +PYBIND11_INLINE void register_local_exception_translator(ExceptionTranslator &&translator) { + detail::with_exception_translators( + [&](std::forward_list &exception_translators, + std::forward_list &local_exception_translators) { + (void) exception_translators; + local_exception_translators.push_front(std::forward(translator)); + }); +} + +PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void print(const tuple &args, const dict &kwargs) { +#if PY_VERSION_HEX >= 0x030D0000 + auto builtins = reinterpret_steal(PyEval_GetFrameBuiltins()); +#else + auto builtins = reinterpret_borrow(PyEval_GetBuiltins()); +#endif + // The builtins dictionary may already be partially cleared during interpreter shutdown. + auto native_print = reinterpret_steal(dict_getitemstringref(builtins.ptr(), "print")); + if (!native_print) { + return; + } + auto result + = reinterpret_steal(PyObject_Call(native_print.ptr(), args.ptr(), kwargs.ptr())); + if (!result) { + throw error_already_set(); + } +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_INLINE void +error_already_set::m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr) { + gil_scoped_acquire gil; + error_scope scope; + delete raw_ptr; +} + +PYBIND11_INLINE const char *error_already_set::what() const noexcept { + gil_scoped_acquire gil; + error_scope scope; + return m_fetched_error->error_string().c_str(); +} + +PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_INLINE function get_type_override(const void *this_ptr, + const type_info *this_type, + const char *name) { + handle self = get_object_handle(this_ptr, this_type); + if (!self) { + return function(); + } + handle type = type::handle_of(self); + auto key = std::make_pair(type.ptr(), name); + + /* Cache functions that aren't overridden in Python to avoid + many costly Python dictionary lookups below */ + bool not_overridden = with_internals([&key](internals &internals) { + auto &cache = internals.inactive_override_cache; + return cache.find(key) != cache.end(); + }); + if (not_overridden) { + return function(); + } + + function override = getattr(self, name, function()); + if (override.is_cpp_function()) { + with_internals([&](internals &internals) { + internals.inactive_override_cache.insert(std::move(key)); + }); + return function(); + } + + /* Don't call dispatch code if invoked from overridden function. + Unfortunately this doesn't work on PyPy and GraalPy. */ +#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) + PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get()); + if (frame != nullptr) { + PyCodeObject *f_code = PyFrame_GetCode(frame); + // f_code is guaranteed to not be NULL + if (std::string(str(f_code->co_name)) == name && f_code->co_argcount > 0) { +# if PY_VERSION_HEX >= 0x030d0000 + PyObject *locals = PyEval_GetFrameLocals(); +# else + PyObject *locals = PyEval_GetLocals(); + Py_XINCREF(locals); +# endif + if (locals != nullptr) { +# if PY_VERSION_HEX >= 0x030b0000 + PyObject *co_varnames = PyCode_GetVarnames(f_code); +# else + PyObject *co_varnames = PyObject_GetAttrString((PyObject *) f_code, "co_varnames"); +# endif + PyObject *self_arg = PyTuple_GET_ITEM(co_varnames, 0); + Py_DECREF(co_varnames); + PyObject *self_caller = dict_getitem(locals, self_arg); + Py_DECREF(locals); + if (self_caller == self.ptr()) { + Py_DECREF(f_code); + Py_DECREF(frame); + return function(); + } + } + } + Py_DECREF(f_code); + Py_DECREF(frame); + } + +#else + /* PyPy currently doesn't provide a detailed cpyext emulation of + frame objects, so we have to emulate this using Python. This + is going to be slow..*/ + dict d; + d["self"] = self; + d["name"] = pybind11::str(name); + PyObject *result + = PyRun_String("import inspect\n" + "frame = inspect.currentframe()\n" + "if frame is not None:\n" + " frame = frame.f_back\n" + " if frame is not None and str(frame.f_code.co_name) == name and " + "frame.f_code.co_argcount > 0:\n" + " self_caller = frame.f_locals[frame.f_code.co_varnames[0]]\n" + " if self_caller == self:\n" + " self = None\n", + Py_file_input, + d.ptr(), + d.ptr()); + if (result == nullptr) + throw error_already_set(); + Py_DECREF(result); + if (d["self"].is_none()) + return function(); +#endif + + return override; +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE cpp_function::unique_function_record +cpp_function::make_function_record() { + return unique_function_record(new detail::function_record()); +} + +PYBIND11_INLINE void cpp_function::initialize_generic(unique_function_record &&unique_rec, + const char *text, + const std::type_info *const *types, + size_t args) { + // Do NOT receive `unique_rec` by value. If this function fails to move out the unique_ptr, + // we do not want this to destruct the pointer. `initialize` (the caller) still relies on + // the pointee being alive after this call. Only move out if a `capsule` is going to keep + // it alive. + auto *rec = unique_rec.get(); + + // Keep track of strdup'ed strings, and clean them up as long as the function's capsule + // has not taken ownership yet (when `unique_rec.release()` is called). + // Note: This cannot easily be fixed by a `unique_ptr` with custom deleter, because the + // strings are only referenced before strdup'ing. So only *after* the following block could + // `destruct` safely be called, but even then, `repr` could still throw in the middle of + // copying all strings. + strdup_guard guarded_strdup; + + /* Create copies of all referenced C-style strings */ + rec->name = guarded_strdup(rec->name ? rec->name : ""); + if (rec->doc) { + rec->doc = guarded_strdup(rec->doc); + } + for (auto &a : rec->args) { + if (a.name) { + a.name = guarded_strdup(a.name); + } + if (a.descr) { + a.descr = guarded_strdup(a.descr); + } else if (a.value) { + a.descr = guarded_strdup(repr(a.value).cast().c_str()); + } + } + + rec->is_constructor = (std::strcmp(rec->name, "__init__") == 0) + || (std::strcmp(rec->name, "__setstate__") == 0); + +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(PYBIND11_DISABLE_NEW_STYLE_INIT_WARNING) + if (rec->is_constructor && !rec->is_new_style_constructor) { + const auto class_name + = detail::get_fully_qualified_tp_name((PyTypeObject *) rec->scope.ptr()); + const auto func_name = std::string(rec->name); + PyErr_WarnEx(PyExc_FutureWarning, + ("pybind11-bound class '" + class_name + + "' is using an old-style " + "placement-new '" + + func_name + + "' which has been deprecated. See " + "the upgrade guide in pybind11's docs. This message is only visible " + "when compiled in debug mode.") + .c_str(), + 0); + } +#endif + + size_t type_index = 0, arg_index = 0; + std::string signature + = detail::generate_function_signature(text, rec, types, type_index, arg_index); + + if (arg_index != args - rec->has_args - rec->has_kwargs || types[type_index] != nullptr) { + pybind11_fail("Internal error while parsing type signature (2)"); + } + + rec->signature = guarded_strdup(signature.c_str()); + rec->args.shrink_to_fit(); + rec->nargs = static_cast(args); + + if (rec->sibling && PYBIND11_INSTANCE_METHOD_CHECK(rec->sibling.ptr())) { + rec->sibling = PYBIND11_INSTANCE_METHOD_GET_FUNCTION(rec->sibling.ptr()); + } + + detail::function_record *chain = nullptr, *chain_start = rec; + if (rec->sibling) { + if (PyCFunction_Check(rec->sibling.ptr())) { + auto *self = PyCFunction_GET_SELF(rec->sibling.ptr()); + if (self == nullptr) { + pybind11_fail("initialize_generic: Unexpected nullptr from PyCFunction_GET_SELF"); + } + chain = detail::function_record_ptr_from_PyObject(self); + if (chain && !chain->scope.is(rec->scope)) { + /* Never append a method to an overload chain of a parent class; + instead, hide the parent's overloads in this case */ + chain = nullptr; + } + } + // Don't trigger for things like the default __init__, which are wrapper_descriptors + // that we are intentionally replacing + else if (!rec->sibling.is_none() && rec->name[0] != '_') { + pybind11_fail("Cannot overload existing non-function object \"" + + std::string(rec->name) + "\" with a function of the same name"); + } + } + + if (!chain) { + /* No existing overload was found, create a new function object */ + rec->def = new PyMethodDef(); + std::memset(rec->def, 0, sizeof(PyMethodDef)); + rec->def->ml_name = rec->name; + rec->def->ml_meth + = reinterpret_cast(reinterpret_cast(dispatcher)); + rec->def->ml_flags = METH_FASTCALL | METH_KEYWORDS; + + object py_func_rec = detail::function_record_PyObject_New(); + (reinterpret_cast(py_func_rec.ptr()))->cpp_func_rec + = unique_rec.release(); + guarded_strdup.release(); + + object scope_module = detail::get_scope_module(rec->scope); + m_ptr = PyCFunction_NewEx(rec->def, py_func_rec.ptr(), scope_module.ptr()); + if (!m_ptr) { + pybind11_fail("cpp_function::cpp_function(): Could not allocate function object"); + } + } else { + /* Append at the beginning or end of the overload chain */ + m_ptr = rec->sibling.ptr(); + inc_ref(); + if (chain->is_method != rec->is_method) { + pybind11_fail( + "overloading a method with both static and instance methods is not supported; " +#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) + "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more " + "details" +#else + "error while attempting to bind " + + std::string(rec->is_method ? "instance" : "static") + " method " + + std::string(pybind11::str(rec->scope.attr("__name__"))) + "." + + std::string(rec->name) + signature +#endif + ); + } + + if (rec->prepend) { + // Beginning of chain; we need to replace the capsule's current head-of-the-chain + // pointer with this one, then make this one point to the previous head of the + // chain. + chain_start = rec; + rec->next = chain; + auto *py_func_rec = reinterpret_cast( + PyCFunction_GET_SELF(m_ptr)); + py_func_rec->cpp_func_rec = unique_rec.release(); + guarded_strdup.release(); + } else { + // Or end of chain (normal behavior) + chain_start = chain; + while (chain->next) { + chain = chain->next; + } + chain->next = unique_rec.release(); + guarded_strdup.release(); + } + } + + std::string signatures; + int index = 0; + /* Create a nice pydoc rec including all signatures and + docstrings of the functions in the overload chain */ + if (chain && options::show_function_signatures() + && std::strcmp(rec->name, "_pybind11_conduit_v1_") != 0) { + // First a generic signature + signatures += rec->name; + signatures += "(*args, **kwargs)\n"; + signatures += "Overloaded function.\n\n"; + } + // Then specific overload signatures + bool first_user_def = true; + for (auto *it = chain_start; it != nullptr; it = it->next) { + if (options::show_function_signatures() + && std::strcmp(rec->name, "_pybind11_conduit_v1_") != 0) { + if (index > 0) { + signatures += '\n'; + } + if (chain) { + signatures += std::to_string(++index) + ". "; + } + signatures += rec->name; + signatures += it->signature; + signatures += '\n'; + } + if (it->doc && it->doc[0] != '\0' && options::show_user_defined_docstrings()) { + // If we're appending another docstring, and aren't printing function signatures, + // we need to append a newline first: + if (!options::show_function_signatures()) { + if (first_user_def) { + first_user_def = false; + } else { + signatures += '\n'; + } + } + if (options::show_function_signatures()) { + signatures += '\n'; + } + signatures += it->doc; + if (options::show_function_signatures()) { + signatures += '\n'; + } + } + } + + auto *func = reinterpret_cast(m_ptr); + // Install docstring if it's non-empty (when at least one option is enabled) + auto *doc = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str()); + std::free(const_cast(PYBIND11_PYCFUNCTION_GET_DOC(func))); + PYBIND11_PYCFUNCTION_SET_DOC(func, doc); + + if (rec->is_method) { + m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr()); + if (!m_ptr) { + pybind11_fail( + "cpp_function::cpp_function(): Could not allocate instance method object"); + } + Py_DECREF(func); + } +} + +PYBIND11_INLINE void cpp_function::destruct(detail::function_record *rec, bool free_strings) { +// If on Python 3.9, check the interpreter "MICRO" (patch) version. +// If this is running on 3.9.0, we have to work around a bug. +#if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 9 + static bool is_zero = Py_GetVersion()[4] == '0'; +#endif + + while (rec) { + detail::function_record *next = rec->next; + if (rec->free_data) { + rec->free_data(rec); + } + // During initialization, these strings might not have been copied yet, + // so they cannot be freed. Once the function has been created, they can. + // Check `make_function_record` for more details. + if (free_strings) { + std::free(rec->name); + std::free(rec->doc); + std::free(rec->signature); + for (auto &arg : rec->args) { + std::free(const_cast(arg.name)); + std::free(const_cast(arg.descr)); + } + } + for (auto &arg : rec->args) { + arg.value.dec_ref(); + } + if (rec->def) { + std::free(const_cast(rec->def->ml_doc)); +// Python 3.9.0 decref's these in the wrong order; rec->def +// If loaded on 3.9.0, let these leak (use Python 3.9.1 at runtime to fix) +// See https://github.com/python/cpython/pull/22670 +#if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 9 + if (!is_zero) { + delete rec->def; + } +#else + delete rec->def; +#endif + } + delete rec; + rec = next; + } +} + +PYBIND11_INLINE PyObject *cpp_function::dispatcher(PyObject *self, + PyObject *const *args_in_arr, + size_t nargsf, + PyObject *kwnames_in) { + using namespace detail; + const function_record *overloads = function_record_ptr_from_PyObject(self); + assert(overloads != nullptr); + + /* Iterator over the list of potentially admissible overloads */ + const function_record *current_overload = overloads; + + /* Need to know how many arguments + keyword arguments there are to pick the right + overload */ + const auto n_args_in = static_cast(PyVectorcall_NARGS(nargsf)); + + handle parent = n_args_in > 0 ? args_in_arr[0] : nullptr, result = PYBIND11_TRY_NEXT_OVERLOAD; + + auto self_value_and_holder = value_and_holder(); + if (overloads->is_constructor) { + if (!parent + || !PyObject_TypeCheck(parent.ptr(), (PyTypeObject *) overloads->scope.ptr())) { + set_error(PyExc_TypeError, + "__init__(self, ...) called with invalid or missing `self` argument"); + return nullptr; + } + + auto *const tinfo + = get_type_info(reinterpret_cast(overloads->scope.ptr())); + auto *const pi = reinterpret_cast(parent.ptr()); + self_value_and_holder = pi->get_value_and_holder(tinfo, true); + + // If this value is already registered it must mean __init__ is invoked multiple times; + // we really can't support that in C++, so just ignore the second __init__. + if (self_value_and_holder.instance_registered()) { + return none().release().ptr(); + } + } + + try { + // We do this in two passes: in the first pass, we load arguments with `convert=false`; + // in the second, we allow conversion (except for arguments with an explicit + // py::arg().noconvert()). This lets us prefer calls without conversion, with + // conversion as a fallback. + std::vector second_pass; + + // However, if there are no overloads, we can just skip the no-convert pass entirely + const bool overloaded = current_overload != nullptr && current_overload->next != nullptr; + + for (; current_overload != nullptr; current_overload = current_overload->next) { + + /* For each overload: + 1. Copy all positional arguments we were given, also checking to make sure that + named positional arguments weren't *also* specified via kwarg. + 2. If we weren't given enough, try to make up the omitted ones by checking + whether they were provided by a kwarg matching the `py::arg("name")` name. If + so, use it (and remove it from kwargs); if not, see if the function binding + provided a default that we can use. + 3. Ensure that either all keyword arguments were "consumed", or that the + function takes a kwargs argument to accept unconsumed kwargs. + 4. Any positional arguments still left get put into a tuple (for args), and any + leftover kwargs get put into a dict. + 5. Pack everything into a vector; if we have py::args or py::kwargs, they are an + extra tuple or dict at the end of the positional arguments. + 6. Call the function call dispatcher (function_record::impl) + + If one of these fail, move on to the next overload and keep trying until we get + a result other than PYBIND11_TRY_NEXT_OVERLOAD. + */ + + const function_record &func = *current_overload; + size_t num_args = func.nargs; // Number of positional arguments that we need + if (func.has_args) { + --num_args; // (but don't count py::args + } + if (func.has_kwargs) { + --num_args; // or py::kwargs) + } + size_t pos_args = func.nargs_pos; + + if (!func.has_args && n_args_in > pos_args) { + continue; // Too many positional arguments for this overload + } + + if (n_args_in < pos_args && func.args.size() < pos_args) { + continue; // Not enough positional arguments given, and not enough defaults to + // fill in the blanks + } + + function_call call(func, parent); + + // Protect std::min with parentheses + size_t args_to_copy = (std::min) (pos_args, n_args_in); + size_t args_copied = 0; + + // 0. Inject new-style `self` argument + if (func.is_new_style_constructor) { + // The `value` may have been preallocated by an old-style `__init__` + // if it was a preceding candidate for overload resolution. + if (self_value_and_holder) { + self_value_and_holder.type->dealloc(self_value_and_holder); + } + + call.init_self = args_in_arr[0]; + call.args.emplace_back(reinterpret_cast(&self_value_and_holder)); + call.args_convert.push_back(false); + ++args_copied; + } + + // 1. Copy any position arguments given. + bool bad_arg = false; + for (; args_copied < args_to_copy; ++args_copied) { + const argument_record *arg_rec + = args_copied < func.args.size() ? &func.args[args_copied] : nullptr; + + /* if the argument is listed in the call site's kwargs, but the argument is + also fulfilled positionally, then the call can't match this overload. for + example, the call site is: foo(0, key=1) but our overload is foo(key:int) then + this call can't be for us, because it would be invalid. + */ + if (kwnames_in && arg_rec && arg_rec->name + && keyword_index(kwnames_in, arg_rec->name) >= 0) { + bad_arg = true; + break; + } + + handle arg(args_in_arr[args_copied]); + if (arg_rec && !arg_rec->none && arg.is_none()) { + bad_arg = true; + break; + } + + call.args.push_back(arg); + call.args_convert.push_back(arg_rec ? arg_rec->convert : true); + } + if (bad_arg) { + continue; // Maybe it was meant for another overload (issue #688) + } + + // Keep track of how many position args we copied out in case we need to come back + // to copy the rest into a py::args argument. + size_t positional_args_copied = args_copied; + + // 1.5. Fill in any missing pos_only args from defaults if they exist + if (args_copied < func.nargs_pos_only) { + for (; args_copied < func.nargs_pos_only; ++args_copied) { + const auto &arg_rec = func.args[args_copied]; + if (arg_rec.value) { + call.args.push_back(arg_rec.value); + call.args_convert.push_back(arg_rec.convert); + } else { + break; + } + } + + if (args_copied < func.nargs_pos_only) { + continue; // Not enough defaults to fill the positional arguments + } + } + + // 2. Check kwargs and, failing that, defaults that may help complete the list + small_vector used_kwargs( + kwnames_in ? static_cast(PyTuple_GET_SIZE(kwnames_in)) : 0, false); + size_t used_kwargs_count = 0; + if (args_copied < num_args) { + for (; args_copied < num_args; ++args_copied) { + const auto &arg_rec = func.args[args_copied]; + + handle value; + if (kwnames_in && arg_rec.name) { + ssize_t i = keyword_index(kwnames_in, arg_rec.name); + if (i >= 0) { + value = args_in_arr[n_args_in + static_cast(i)]; + used_kwargs.set(static_cast(i), true); + used_kwargs_count++; + } + } + + if (!value) { + value = arg_rec.value; + if (!value) { + break; + } + } + + if (!arg_rec.none && value.is_none()) { + break; + } + + // If we're at the py::args index then first insert a stub for it to be + // replaced later + if (func.has_args && call.args.size() == func.nargs_pos) { + call.args.push_back(none()); + } + + call.args.push_back(value); + call.args_convert.push_back(arg_rec.convert); + } + + if (args_copied < num_args) { + continue; // Not enough arguments, defaults, or kwargs to fill the + // positional arguments + } + } + + // 3. Check everything was consumed (unless we have a kwargs arg) + if (!func.has_kwargs && used_kwargs_count < used_kwargs.size()) { + continue; // Unconsumed kwargs, but no py::kwargs argument to accept them + } + + // 4a. If we have a py::args argument, create a new tuple with leftovers + if (func.has_args) { + if (positional_args_copied >= n_args_in) { + call.args_ref = tuple(0); + } else { + size_t args_size = n_args_in - positional_args_copied; + tuple extra_args(args_size); + for (size_t i = 0; i < args_size; ++i) { + extra_args[i] = args_in_arr[positional_args_copied + i]; + } + call.args_ref = std::move(extra_args); + } + if (call.args.size() <= func.nargs_pos) { + call.args.push_back(call.args_ref); + } else { + call.args[func.nargs_pos] = call.args_ref; + } + call.args_convert.push_back(false); + } + + // 4b. If we have a py::kwargs, pass on any remaining kwargs + if (func.has_kwargs) { + dict kwargs; + for (size_t i = 0; i < used_kwargs.size(); ++i) { + if (!used_kwargs[i]) { + // Cast values into handles before indexing into kwargs to ensure + // well-defined evaluation order (MSVC C4866). + handle arg_in_arr = args_in_arr[n_args_in + i], + kwname = PyTuple_GET_ITEM(kwnames_in, i); + kwargs[kwname] = arg_in_arr; + } + } + call.args.push_back(kwargs); + call.args_convert.push_back(false); + call.kwargs_ref = std::move(kwargs); + } + + // 5. Put everything in a vector. Not technically step 5, we've been building it + // in `call.args` all along. + +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + if (call.args.size() != func.nargs || call.args_convert.size() != func.nargs) { + pybind11_fail("Internal error: function call dispatcher inserted wrong number " + "of arguments!"); + } +#endif + + args_convert_vector second_pass_convert; + if (overloaded) { + // We're in the first no-convert pass, so swap out the conversion flags for a + // set of all-false flags. If the call fails, we'll swap the flags back in for + // the conversion-allowed call below. + second_pass_convert = std::move(call.args_convert); + call.args_convert = args_convert_vector(func.nargs, false); + } + + // 6. Call the function. + try { + loader_life_support guard{}; + result = func.impl(call); + } catch (reference_cast_error &) { + result = PYBIND11_TRY_NEXT_OVERLOAD; + } + + if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) { + break; + } + + if (overloaded) { + // The (overloaded) call failed; if the call has at least one argument that + // permits conversion (i.e. it hasn't been explicitly specified `.noconvert()`) + // then add this call to the list of second pass overloads to try. + for (size_t i = func.is_method ? 1 : 0; i < pos_args; i++) { + if (second_pass_convert[i]) { + // Found one: swap the converting flags back in and store the call for + // the second pass. + call.args_convert.swap(second_pass_convert); + second_pass.push_back(std::move(call)); + break; + } + } + } + } + + if (overloaded && !second_pass.empty() && result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) { + // The no-conversion pass finished without success, try again with conversion + // allowed + for (auto &call : second_pass) { + try { + loader_life_support guard{}; + result = call.func.impl(call); + } catch (reference_cast_error &) { + result = PYBIND11_TRY_NEXT_OVERLOAD; + } + + if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) { + // The error reporting logic below expects 'current_overload' to be valid, + // as it would be if we'd encountered this failure in the first-pass loop. + if (!result) { + current_overload = &call.func; + } + break; + } + } + } + } catch (error_already_set &e) { + e.restore(); + return nullptr; +#ifdef __GLIBCXX__ + } catch (abi::__forced_unwind &) { + throw; +#endif + } catch (...) { + try_translate_exceptions(); + return nullptr; + } + + auto append_note_if_missing_header_is_suspected = [](std::string &msg) { + if (msg.find("std::") != std::string::npos) { + msg += "\n\n" + "Did you forget to `#include `? Or ,\n" + ", , etc. Some automatic\n" + "conversions are optional and require extra headers to be included\n" + "when compiling your pybind11 module."; + } + }; + + if (result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) { + if (overloads->is_operator) { + return handle(Py_NotImplemented).inc_ref().ptr(); + } + + std::string msg = std::string(overloads->name) + "(): incompatible " + + std::string(overloads->is_constructor ? "constructor" : "function") + + " arguments. The following argument types are supported:\n"; + + int ctr = 0; + for (const function_record *it2 = overloads; it2 != nullptr; it2 = it2->next) { + msg += " " + std::to_string(++ctr) + ". "; + + bool wrote_sig = false; + if (overloads->is_constructor) { + // For a constructor, rewrite `(self: Object, arg0, ...) -> NoneType` as + // `Object(arg0, ...)` + std::string sig = it2->signature; + size_t start = sig.find('(') + 7; // skip "(self: " + if (start < sig.size()) { + // End at the , for the next argument + size_t end = sig.find(", "), next = end + 2; + size_t ret = sig.rfind(" -> "); + // Or the ), if there is no comma: + if (end >= sig.size()) { + next = end = sig.find(')'); + } + if (start < end && next < sig.size()) { + msg.append(sig, start, end - start); + msg += '('; + msg.append(sig, next, ret - next); + wrote_sig = true; + } + } + } + if (!wrote_sig) { + msg += it2->signature; + } + + msg += '\n'; + } + msg += "\nInvoked with: "; + bool some_args = false; + for (size_t ti = overloads->is_constructor ? 1 : 0; ti < n_args_in; ++ti) { + if (!some_args) { + some_args = true; + } else { + msg += ", "; + } + try { + msg += pybind11::repr(args_in_arr[ti]); + } catch (const error_already_set &) { + msg += ""; + } + } + if (kwnames_in && PyTuple_GET_SIZE(kwnames_in) > 0) { + if (some_args) { + msg += "; "; + } + msg += "kwargs: "; + bool first = true; + for (size_t i = 0; i < static_cast(PyTuple_GET_SIZE(kwnames_in)); ++i) { + if (first) { + first = false; + } else { + msg += ", "; + } + msg += reinterpret_borrow(PyTuple_GET_ITEM(kwnames_in, i)); + msg += '='; + try { + msg += pybind11::repr(args_in_arr[n_args_in + i]); + } catch (const error_already_set &) { + msg += ""; + } + } + } + + append_note_if_missing_header_is_suspected(msg); + // Attach additional error info to the exception if supported + if (PyErr_Occurred()) { + // #HelpAppreciated: unit test coverage for this branch. + raise_from(PyExc_TypeError, msg.c_str()); + return nullptr; + } + set_error(PyExc_TypeError, msg.c_str()); + return nullptr; + } + if (!result) { + std::string msg = "Unable to convert function return value to a " + "Python type! The signature was\n\t"; + assert(current_overload != nullptr); + msg += current_overload->signature; + append_note_if_missing_header_is_suspected(msg); + // Attach additional error info to the exception if supported + if (PyErr_Occurred()) { + raise_from(PyExc_TypeError, msg.c_str()); + return nullptr; + } + set_error(PyExc_TypeError, msg.c_str()); + return nullptr; + } + if (overloads->is_constructor && !self_value_and_holder.holder_constructed()) { + auto *pi = reinterpret_cast(parent.ptr()); + self_value_and_holder.type->init_instance(pi, nullptr); + } + return result.ptr(); +} + +PYBIND11_NAMESPACE_BEGIN(detail) + +PYBIND11_INLINE void generic_type::initialize(const type_record &rec) { + if (rec.scope && hasattr(rec.scope, "__dict__") + && rec.scope.attr("__dict__").contains(rec.name)) { + pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) + + "\": an object with that name is already defined"); + } + + if ((rec.module_local ? get_local_type_info(*rec.type) : get_global_type_info(*rec.type)) + != nullptr) { + pybind11_fail("generic_type: type \"" + std::string(rec.name) + + "\" is already registered!"); + } + + m_ptr = make_new_python_type(rec); + + /* Register supplemental type information in C++ dict */ + auto *tinfo = new detail::type_info(); + tinfo->type = reinterpret_cast(m_ptr); + tinfo->cpptype = rec.type; + tinfo->type_size = rec.type_size; + tinfo->type_align = rec.type_align; + tinfo->operator_new = rec.operator_new; + tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size); + tinfo->init_instance = rec.init_instance; + tinfo->dealloc = rec.dealloc; + tinfo->get_trampoline_self_life_support = rec.get_trampoline_self_life_support; + tinfo->simple_type = true; + tinfo->simple_ancestors = true; + tinfo->module_local = rec.module_local; + tinfo->holder_enum_v = rec.holder_enum_v; + + 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) { + 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 +#if defined(__GNUC__) && __GNUC__ == 12 + // When using GCC 12 these warnings are disabled as they trigger + // false positive warnings. Discussed here: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115824. + PYBIND11_WARNING_DISABLE_GCC("-Warray-bounds") + PYBIND11_WARNING_DISABLE_GCC("-Wstringop-overread") +#endif + internals.registered_types_py[reinterpret_cast(m_ptr)] = {tinfo}; + PYBIND11_WARNING_POP + }); + + if (rec.bases.size() > 1 || rec.multiple_inheritance) { + mark_parents_nonsimple(tinfo->type); + tinfo->simple_ancestors = false; + } else if (rec.bases.size() == 1) { + auto *parent_tinfo = get_type_info(reinterpret_cast(rec.bases[0].ptr())); + assert(parent_tinfo != nullptr); + bool parent_simple_ancestors = parent_tinfo->simple_ancestors; + tinfo->simple_ancestors = parent_simple_ancestors; + // The parent can no longer be a simple type if it has MI and has a child + parent_tinfo->simple_type = parent_tinfo->simple_type && parent_simple_ancestors; + } + + if (rec.module_local) { + // Stash the local typeinfo and loader so that external modules can access it. + tinfo->module_local_load = &type_caster_generic::local_load; + setattr(m_ptr, PYBIND11_MODULE_LOCAL_ID, capsule(tinfo)); + } +} + +PYBIND11_INLINE void generic_type::mark_parents_nonsimple(PyTypeObject *value) { + auto t = reinterpret_borrow(value->tp_bases); + for (handle h : t) { + auto *tinfo2 = get_type_info(reinterpret_cast(h.ptr())); + if (tinfo2) { + tinfo2->simple_type = false; + } + mark_parents_nonsimple(reinterpret_cast(h.ptr())); + } +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void enum_base::init(bool is_arithmetic, + bool is_convertible) { + m_base.attr("__entries") = dict(); + auto property = handle(reinterpret_cast(&PyProperty_Type)); + auto static_property + = handle(reinterpret_cast(get_internals().static_property_type)); + + m_base.attr("__repr__") = cpp_function( + [](const object &arg) -> str { + handle type = type::handle_of(arg); + object type_name = type.attr("__name__"); + return pybind11::str("<{}.{}: {}>") + .format(std::move(type_name), enum_name(arg), int_(arg)); + }, + name("__repr__"), + is_method(m_base), + pos_only()); + + m_base.attr("name") + = property(cpp_function(&enum_name, name("name"), is_method(m_base), pos_only())); + + m_base.attr("__str__") = cpp_function( + [](handle arg) -> str { + object type_name = type::handle_of(arg).attr("__name__"); + return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg)); + }, + name("__str__"), + is_method(m_base), + pos_only()); + + if (options::show_enum_members_docstring()) { + m_base.attr("__doc__") = static_property( + cpp_function( + [](handle arg) -> std::string { + std::string docstring; + dict entries = arg.attr("__entries"); + if ((reinterpret_cast(arg.ptr()))->tp_doc) { + docstring + += std::string(reinterpret_cast(arg.ptr())->tp_doc); + docstring += "\n\n"; + } + docstring += "Members:"; + for (auto kv : entries) { + auto key = std::string(pybind11::str(kv.first)); + auto comment = kv.second[int_(1)]; + docstring += "\n\n "; + docstring += key; + if (!comment.is_none()) { + docstring += " : "; + docstring += pybind11::str(comment).cast(); + } + } + return docstring; + }, + name("__doc__")), + none(), + none(), + ""); + } + + m_base.attr("__members__") = static_property(cpp_function( + [](handle arg) -> dict { + dict entries = arg.attr("__entries"), m; + for (auto kv : entries) { + m[kv.first] = kv.second[int_(0)]; + } + return m; + }, + name("__members__")), + none(), + none(), + ""); + +#define PYBIND11_ENUM_OP_STRICT(op, expr, strict_behavior) \ + m_base.attr(op) = cpp_function( \ + [](const object &a, const object &b) { \ + if (!type::handle_of(a).is(type::handle_of(b))) \ + strict_behavior; /* NOLINT(bugprone-macro-parentheses) */ \ + return expr; \ + }, \ + name(op), \ + is_method(m_base), \ + arg("other"), \ + pos_only()) + +#define PYBIND11_ENUM_OP_CONV(op, expr) \ + m_base.attr(op) = cpp_function( \ + [](const object &a_, const object &b_) { \ + int_ a(a_), b(b_); \ + return expr; \ + }, \ + name(op), \ + is_method(m_base), \ + arg("other"), \ + pos_only()) + +#define PYBIND11_ENUM_OP_CONV_LHS(op, expr) \ + m_base.attr(op) = cpp_function( \ + [](const object &a_, const object &b) { \ + int_ a(a_); \ + return expr; \ + }, \ + name(op), \ + is_method(m_base), \ + arg("other"), \ + pos_only()) + + if (is_convertible) { + if (is_arithmetic) { + m_base.attr("__invert__") + = cpp_function([](const object &arg) { return ~(int_(arg)); }, + name("__invert__"), + is_method(m_base), + pos_only()); + } + } + +#undef PYBIND11_ENUM_OP_CONV_LHS +#undef PYBIND11_ENUM_OP_CONV +#undef PYBIND11_ENUM_OP_STRICT + + m_base.attr("__getstate__") = cpp_function([](const object &arg) { return int_(arg); }, + name("__getstate__"), + is_method(m_base), + pos_only()); + + m_base.attr("__hash__") = cpp_function([](const object &arg) { return int_(arg); }, + name("__hash__"), + is_method(m_base), + pos_only()); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void +enum_base::value(char const *name_, object value, const char *doc) { + dict entries = m_base.attr("__entries"); + str name(name_); + if (entries.contains(name)) { + std::string type_name = std::string(str(m_base.attr("__name__"))); + throw value_error(std::move(type_name) + ": element \"" + std::string(name_) + + "\" already exists!"); + } + + entries[name] = pybind11::make_tuple(value, doc); + m_base.attr(std::move(name)) = std::move(value); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void enum_base::export_values() { + dict entries = m_base.attr("__entries"); + for (auto kv : entries) { + m_parent.attr(kv.first) = kv.second[int_(0)]; + } +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index eebb130694..7126cb501c 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -67,181 +66,14 @@ PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) -inline std::string replace_newlines_and_squash(const char *text) { - const char *whitespaces = " \t\n\r\f\v"; - std::string result(text); - bool previous_is_whitespace = false; - - if (result.size() >= 2) { - // Do not modify string representations - char first_char = result[0]; - char last_char = result[result.size() - 1]; - if (first_char == last_char && first_char == '\'') { - return result; - } - } - result.clear(); - - // Replace characters in whitespaces array with spaces and squash consecutive spaces - while (*text != '\0') { - if (std::strchr(whitespaces, *text)) { - if (!previous_is_whitespace) { - result += ' '; - previous_is_whitespace = true; - } - } else { - result += *text; - previous_is_whitespace = false; - } - ++text; - } - - // Strip leading and trailing whitespaces - const size_t str_begin = result.find_first_not_of(whitespaces); - if (str_begin == std::string::npos) { - return ""; - } - - const size_t str_end = result.find_last_not_of(whitespaces); - const size_t str_range = str_end - str_begin + 1; - - return result.substr(str_begin, str_range); -} +std::string replace_newlines_and_squash(const char *text); /* Generate a proper function signature */ -inline std::string generate_function_signature(const char *type_caster_name_field, - detail::function_record *func_rec, - const std::type_info *const *types, - size_t &type_index, - size_t &arg_index) { - std::string signature; - bool is_starred = false; - // `is_return_value.top()` is true if we are currently inside the return type of the - // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops - // back to the previous state. - std::stack is_return_value({false}); - // The following characters have special meaning in the signature parsing. Literals - // containing these are escaped with `!`. - std::string special_chars("!@%{}-"); - for (const auto *pc = type_caster_name_field; *pc != '\0'; ++pc) { - const auto c = *pc; - if (c == '{') { - // Write arg name for everything except *args and **kwargs. - // Detect {@*args...} or {@**kwargs...} - is_starred = *(pc + 1) == '@' && *(pc + 2) == '*'; - if (is_starred) { - continue; - } - // Separator for keyword-only arguments, placed before the kw - // arguments start (unless we are already putting an *args) - if (!func_rec->has_args && arg_index == func_rec->nargs_pos) { - signature += "*, "; - } - if (arg_index < func_rec->args.size() && func_rec->args[arg_index].name) { - signature += func_rec->args[arg_index].name; - } else if (arg_index == 0 && func_rec->is_method) { - signature += "self"; - } else { - signature += "arg" + std::to_string(arg_index - (func_rec->is_method ? 1 : 0)); - } - signature += ": "; - } else if (c == '}') { - // Write default value if available. - if (!is_starred && arg_index < func_rec->args.size() - && func_rec->args[arg_index].descr) { - signature += " = "; - signature += detail::replace_newlines_and_squash(func_rec->args[arg_index].descr); - } - // Separator for positional-only arguments (placed after the - // argument, rather than before like * - if (func_rec->nargs_pos_only > 0 && (arg_index + 1) == func_rec->nargs_pos_only) { - signature += ", /"; - } - if (!is_starred) { - arg_index++; - } - } else if (c == '%') { - const std::type_info *t = types[type_index++]; - if (!t) { - pybind11_fail("Internal error while parsing type signature (1)"); - } - if (auto *tinfo = detail::get_type_info(*t)) { - handle th(reinterpret_cast(tinfo->type)); - signature += th.attr("__module__").cast() + "." - + th.attr("__qualname__").cast(); - } else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) { - signature += th.attr("__module__").cast() + "." - + th.attr("__qualname__").cast(); - } else if (func_rec->is_new_style_constructor && arg_index == 0) { - // A new-style `__init__` takes `self` as `value_and_holder`. - // Rewrite it to the proper class type. - signature += func_rec->scope.attr("__module__").cast() + "." - + func_rec->scope.attr("__qualname__").cast(); - } else { - signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name())); - } - } else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { - // typing::Literal escapes special characters with ! - signature += *++pc; - } else if (c == '@') { - // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see - // typing::Callable/detail::arg_descr/detail::return_descr). - // `@~ ... @!` inverts the current context (see detail::inv_descr). - if (*(pc + 1) == '^') { - is_return_value.emplace(false); - ++pc; - continue; - } - if (*(pc + 1) == '$') { - is_return_value.emplace(true); - ++pc; - continue; - } - if (*(pc + 1) == '~') { - is_return_value.emplace(!is_return_value.top()); - ++pc; - continue; - } - if (*(pc + 1) == '!') { - is_return_value.pop(); - ++pc; - continue; - } - // Handle types that differ depending on whether they appear - // in an argument or a return value position (see io_name). - // For named arguments (py::arg()) with noconvert set, return value type is used. - ++pc; - if (!is_return_value.top() - && (!(arg_index < func_rec->args.size() && !func_rec->args[arg_index].convert))) { - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - ++pc; - } - } else { - while (*pc != '\0' && *pc != '@') { - ++pc; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - } - } else { - if (c == '-' && *(pc + 1) == '>') { - is_return_value.emplace(true); - } - signature += c; - } - } - return signature; -} +std::string generate_function_signature(const char *type_caster_name_field, + detail::function_record *func_rec, + const std::type_info *const *types, + size_t &type_index, + size_t &arg_index); template inline std::string generate_type_signature() { @@ -479,9 +311,7 @@ class cpp_function : public function { = std::unique_ptr; /// Space optimization: don't inline this frequently instantiated fragment - PYBIND11_NOINLINE unique_function_record make_function_record() { - return unique_function_record(new detail::function_record()); - } + unique_function_record make_function_record(); private: // This is outlined from the dispatch lambda in initialize to save @@ -703,711 +533,16 @@ class cpp_function : public function { void initialize_generic(unique_function_record &&unique_rec, const char *text, const std::type_info *const *types, - size_t args) { - // Do NOT receive `unique_rec` by value. If this function fails to move out the unique_ptr, - // we do not want this to destruct the pointer. `initialize` (the caller) still relies on - // the pointee being alive after this call. Only move out if a `capsule` is going to keep - // it alive. - auto *rec = unique_rec.get(); - - // Keep track of strdup'ed strings, and clean them up as long as the function's capsule - // has not taken ownership yet (when `unique_rec.release()` is called). - // Note: This cannot easily be fixed by a `unique_ptr` with custom deleter, because the - // strings are only referenced before strdup'ing. So only *after* the following block could - // `destruct` safely be called, but even then, `repr` could still throw in the middle of - // copying all strings. - strdup_guard guarded_strdup; - - /* Create copies of all referenced C-style strings */ - rec->name = guarded_strdup(rec->name ? rec->name : ""); - if (rec->doc) { - rec->doc = guarded_strdup(rec->doc); - } - for (auto &a : rec->args) { - if (a.name) { - a.name = guarded_strdup(a.name); - } - if (a.descr) { - a.descr = guarded_strdup(a.descr); - } else if (a.value) { - a.descr = guarded_strdup(repr(a.value).cast().c_str()); - } - } - - rec->is_constructor = (std::strcmp(rec->name, "__init__") == 0) - || (std::strcmp(rec->name, "__setstate__") == 0); - -#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(PYBIND11_DISABLE_NEW_STYLE_INIT_WARNING) - if (rec->is_constructor && !rec->is_new_style_constructor) { - const auto class_name - = detail::get_fully_qualified_tp_name((PyTypeObject *) rec->scope.ptr()); - const auto func_name = std::string(rec->name); - PyErr_WarnEx(PyExc_FutureWarning, - ("pybind11-bound class '" + class_name - + "' is using an old-style " - "placement-new '" - + func_name - + "' which has been deprecated. See " - "the upgrade guide in pybind11's docs. This message is only visible " - "when compiled in debug mode.") - .c_str(), - 0); - } -#endif - - size_t type_index = 0, arg_index = 0; - std::string signature - = detail::generate_function_signature(text, rec, types, type_index, arg_index); - - if (arg_index != args - rec->has_args - rec->has_kwargs || types[type_index] != nullptr) { - pybind11_fail("Internal error while parsing type signature (2)"); - } - - rec->signature = guarded_strdup(signature.c_str()); - rec->args.shrink_to_fit(); - rec->nargs = static_cast(args); - - if (rec->sibling && PYBIND11_INSTANCE_METHOD_CHECK(rec->sibling.ptr())) { - rec->sibling = PYBIND11_INSTANCE_METHOD_GET_FUNCTION(rec->sibling.ptr()); - } - - detail::function_record *chain = nullptr, *chain_start = rec; - if (rec->sibling) { - if (PyCFunction_Check(rec->sibling.ptr())) { - auto *self = PyCFunction_GET_SELF(rec->sibling.ptr()); - if (self == nullptr) { - pybind11_fail( - "initialize_generic: Unexpected nullptr from PyCFunction_GET_SELF"); - } - chain = detail::function_record_ptr_from_PyObject(self); - if (chain && !chain->scope.is(rec->scope)) { - /* Never append a method to an overload chain of a parent class; - instead, hide the parent's overloads in this case */ - chain = nullptr; - } - } - // Don't trigger for things like the default __init__, which are wrapper_descriptors - // that we are intentionally replacing - else if (!rec->sibling.is_none() && rec->name[0] != '_') { - pybind11_fail("Cannot overload existing non-function object \"" - + std::string(rec->name) + "\" with a function of the same name"); - } - } - - if (!chain) { - /* No existing overload was found, create a new function object */ - rec->def = new PyMethodDef(); - std::memset(rec->def, 0, sizeof(PyMethodDef)); - rec->def->ml_name = rec->name; - rec->def->ml_meth - = reinterpret_cast(reinterpret_cast(dispatcher)); - rec->def->ml_flags = METH_FASTCALL | METH_KEYWORDS; - - object py_func_rec = detail::function_record_PyObject_New(); - (reinterpret_cast(py_func_rec.ptr()))->cpp_func_rec - = unique_rec.release(); - guarded_strdup.release(); - - object scope_module = detail::get_scope_module(rec->scope); - m_ptr = PyCFunction_NewEx(rec->def, py_func_rec.ptr(), scope_module.ptr()); - if (!m_ptr) { - pybind11_fail("cpp_function::cpp_function(): Could not allocate function object"); - } - } else { - /* Append at the beginning or end of the overload chain */ - m_ptr = rec->sibling.ptr(); - inc_ref(); - if (chain->is_method != rec->is_method) { - pybind11_fail( - "overloading a method with both static and instance methods is not supported; " -#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more " - "details" -#else - "error while attempting to bind " - + std::string(rec->is_method ? "instance" : "static") + " method " - + std::string(pybind11::str(rec->scope.attr("__name__"))) + "." - + std::string(rec->name) + signature -#endif - ); - } - - if (rec->prepend) { - // Beginning of chain; we need to replace the capsule's current head-of-the-chain - // pointer with this one, then make this one point to the previous head of the - // chain. - chain_start = rec; - rec->next = chain; - auto *py_func_rec = reinterpret_cast( - PyCFunction_GET_SELF(m_ptr)); - py_func_rec->cpp_func_rec = unique_rec.release(); - guarded_strdup.release(); - } else { - // Or end of chain (normal behavior) - chain_start = chain; - while (chain->next) { - chain = chain->next; - } - chain->next = unique_rec.release(); - guarded_strdup.release(); - } - } - - std::string signatures; - int index = 0; - /* Create a nice pydoc rec including all signatures and - docstrings of the functions in the overload chain */ - if (chain && options::show_function_signatures() - && std::strcmp(rec->name, "_pybind11_conduit_v1_") != 0) { - // First a generic signature - signatures += rec->name; - signatures += "(*args, **kwargs)\n"; - signatures += "Overloaded function.\n\n"; - } - // Then specific overload signatures - bool first_user_def = true; - for (auto *it = chain_start; it != nullptr; it = it->next) { - if (options::show_function_signatures() - && std::strcmp(rec->name, "_pybind11_conduit_v1_") != 0) { - if (index > 0) { - signatures += '\n'; - } - if (chain) { - signatures += std::to_string(++index) + ". "; - } - signatures += rec->name; - signatures += it->signature; - signatures += '\n'; - } - if (it->doc && it->doc[0] != '\0' && options::show_user_defined_docstrings()) { - // If we're appending another docstring, and aren't printing function signatures, - // we need to append a newline first: - if (!options::show_function_signatures()) { - if (first_user_def) { - first_user_def = false; - } else { - signatures += '\n'; - } - } - if (options::show_function_signatures()) { - signatures += '\n'; - } - signatures += it->doc; - if (options::show_function_signatures()) { - signatures += '\n'; - } - } - } - - auto *func = reinterpret_cast(m_ptr); - // Install docstring if it's non-empty (when at least one option is enabled) - auto *doc = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str()); - std::free(const_cast(PYBIND11_PYCFUNCTION_GET_DOC(func))); - PYBIND11_PYCFUNCTION_SET_DOC(func, doc); - - if (rec->is_method) { - m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr()); - if (!m_ptr) { - pybind11_fail( - "cpp_function::cpp_function(): Could not allocate instance method object"); - } - Py_DECREF(func); - } - } + size_t args); friend void detail::function_record_PyTypeObject_methods::tp_dealloc_impl(PyObject *); /// When a cpp_function is GCed, release any memory allocated by pybind11 - static void destruct(detail::function_record *rec, bool free_strings = true) { -// If on Python 3.9, check the interpreter "MICRO" (patch) version. -// If this is running on 3.9.0, we have to work around a bug. -#if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 9 - static bool is_zero = Py_GetVersion()[4] == '0'; -#endif - - while (rec) { - detail::function_record *next = rec->next; - if (rec->free_data) { - rec->free_data(rec); - } - // During initialization, these strings might not have been copied yet, - // so they cannot be freed. Once the function has been created, they can. - // Check `make_function_record` for more details. - if (free_strings) { - std::free(rec->name); - std::free(rec->doc); - std::free(rec->signature); - for (auto &arg : rec->args) { - std::free(const_cast(arg.name)); - std::free(const_cast(arg.descr)); - } - } - for (auto &arg : rec->args) { - arg.value.dec_ref(); - } - if (rec->def) { - std::free(const_cast(rec->def->ml_doc)); -// Python 3.9.0 decref's these in the wrong order; rec->def -// If loaded on 3.9.0, let these leak (use Python 3.9.1 at runtime to fix) -// See https://github.com/python/cpython/pull/22670 -#if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 9 - if (!is_zero) { - delete rec->def; - } -#else - delete rec->def; -#endif - } - delete rec; - rec = next; - } - } + static void destruct(detail::function_record *rec, bool free_strings = true); /// Main dispatch logic for calls to functions bound using pybind11 static PyObject * - dispatcher(PyObject *self, PyObject *const *args_in_arr, size_t nargsf, PyObject *kwnames_in) { - using namespace detail; - const function_record *overloads = function_record_ptr_from_PyObject(self); - assert(overloads != nullptr); - - /* Iterator over the list of potentially admissible overloads */ - const function_record *current_overload = overloads; - - /* Need to know how many arguments + keyword arguments there are to pick the right - overload */ - const auto n_args_in = static_cast(PyVectorcall_NARGS(nargsf)); - - handle parent = n_args_in > 0 ? args_in_arr[0] : nullptr, - result = PYBIND11_TRY_NEXT_OVERLOAD; - - auto self_value_and_holder = value_and_holder(); - if (overloads->is_constructor) { - if (!parent - || !PyObject_TypeCheck(parent.ptr(), (PyTypeObject *) overloads->scope.ptr())) { - set_error(PyExc_TypeError, - "__init__(self, ...) called with invalid or missing `self` argument"); - return nullptr; - } - - auto *const tinfo - = get_type_info(reinterpret_cast(overloads->scope.ptr())); - auto *const pi = reinterpret_cast(parent.ptr()); - self_value_and_holder = pi->get_value_and_holder(tinfo, true); - - // If this value is already registered it must mean __init__ is invoked multiple times; - // we really can't support that in C++, so just ignore the second __init__. - if (self_value_and_holder.instance_registered()) { - return none().release().ptr(); - } - } - - try { - // We do this in two passes: in the first pass, we load arguments with `convert=false`; - // in the second, we allow conversion (except for arguments with an explicit - // py::arg().noconvert()). This lets us prefer calls without conversion, with - // conversion as a fallback. - std::vector second_pass; - - // However, if there are no overloads, we can just skip the no-convert pass entirely - const bool overloaded - = current_overload != nullptr && current_overload->next != nullptr; - - for (; current_overload != nullptr; current_overload = current_overload->next) { - - /* For each overload: - 1. Copy all positional arguments we were given, also checking to make sure that - named positional arguments weren't *also* specified via kwarg. - 2. If we weren't given enough, try to make up the omitted ones by checking - whether they were provided by a kwarg matching the `py::arg("name")` name. If - so, use it (and remove it from kwargs); if not, see if the function binding - provided a default that we can use. - 3. Ensure that either all keyword arguments were "consumed", or that the - function takes a kwargs argument to accept unconsumed kwargs. - 4. Any positional arguments still left get put into a tuple (for args), and any - leftover kwargs get put into a dict. - 5. Pack everything into a vector; if we have py::args or py::kwargs, they are an - extra tuple or dict at the end of the positional arguments. - 6. Call the function call dispatcher (function_record::impl) - - If one of these fail, move on to the next overload and keep trying until we get - a result other than PYBIND11_TRY_NEXT_OVERLOAD. - */ - - const function_record &func = *current_overload; - size_t num_args = func.nargs; // Number of positional arguments that we need - if (func.has_args) { - --num_args; // (but don't count py::args - } - if (func.has_kwargs) { - --num_args; // or py::kwargs) - } - size_t pos_args = func.nargs_pos; - - if (!func.has_args && n_args_in > pos_args) { - continue; // Too many positional arguments for this overload - } - - if (n_args_in < pos_args && func.args.size() < pos_args) { - continue; // Not enough positional arguments given, and not enough defaults to - // fill in the blanks - } - - function_call call(func, parent); - - // Protect std::min with parentheses - size_t args_to_copy = (std::min) (pos_args, n_args_in); - size_t args_copied = 0; - - // 0. Inject new-style `self` argument - if (func.is_new_style_constructor) { - // The `value` may have been preallocated by an old-style `__init__` - // if it was a preceding candidate for overload resolution. - if (self_value_and_holder) { - self_value_and_holder.type->dealloc(self_value_and_holder); - } - - call.init_self = args_in_arr[0]; - call.args.emplace_back(reinterpret_cast(&self_value_and_holder)); - call.args_convert.push_back(false); - ++args_copied; - } - - // 1. Copy any position arguments given. - bool bad_arg = false; - for (; args_copied < args_to_copy; ++args_copied) { - const argument_record *arg_rec - = args_copied < func.args.size() ? &func.args[args_copied] : nullptr; - - /* if the argument is listed in the call site's kwargs, but the argument is - also fulfilled positionally, then the call can't match this overload. for - example, the call site is: foo(0, key=1) but our overload is foo(key:int) then - this call can't be for us, because it would be invalid. - */ - if (kwnames_in && arg_rec && arg_rec->name - && keyword_index(kwnames_in, arg_rec->name) >= 0) { - bad_arg = true; - break; - } - - handle arg(args_in_arr[args_copied]); - if (arg_rec && !arg_rec->none && arg.is_none()) { - bad_arg = true; - break; - } - - call.args.push_back(arg); - call.args_convert.push_back(arg_rec ? arg_rec->convert : true); - } - if (bad_arg) { - continue; // Maybe it was meant for another overload (issue #688) - } - - // Keep track of how many position args we copied out in case we need to come back - // to copy the rest into a py::args argument. - size_t positional_args_copied = args_copied; - - // 1.5. Fill in any missing pos_only args from defaults if they exist - if (args_copied < func.nargs_pos_only) { - for (; args_copied < func.nargs_pos_only; ++args_copied) { - const auto &arg_rec = func.args[args_copied]; - if (arg_rec.value) { - call.args.push_back(arg_rec.value); - call.args_convert.push_back(arg_rec.convert); - } else { - break; - } - } - - if (args_copied < func.nargs_pos_only) { - continue; // Not enough defaults to fill the positional arguments - } - } - - // 2. Check kwargs and, failing that, defaults that may help complete the list - small_vector used_kwargs( - kwnames_in ? static_cast(PyTuple_GET_SIZE(kwnames_in)) : 0, false); - size_t used_kwargs_count = 0; - if (args_copied < num_args) { - for (; args_copied < num_args; ++args_copied) { - const auto &arg_rec = func.args[args_copied]; - - handle value; - if (kwnames_in && arg_rec.name) { - ssize_t i = keyword_index(kwnames_in, arg_rec.name); - if (i >= 0) { - value = args_in_arr[n_args_in + static_cast(i)]; - used_kwargs.set(static_cast(i), true); - used_kwargs_count++; - } - } - - if (!value) { - value = arg_rec.value; - if (!value) { - break; - } - } - - if (!arg_rec.none && value.is_none()) { - break; - } - - // If we're at the py::args index then first insert a stub for it to be - // replaced later - if (func.has_args && call.args.size() == func.nargs_pos) { - call.args.push_back(none()); - } - - call.args.push_back(value); - call.args_convert.push_back(arg_rec.convert); - } - - if (args_copied < num_args) { - continue; // Not enough arguments, defaults, or kwargs to fill the - // positional arguments - } - } - - // 3. Check everything was consumed (unless we have a kwargs arg) - if (!func.has_kwargs && used_kwargs_count < used_kwargs.size()) { - continue; // Unconsumed kwargs, but no py::kwargs argument to accept them - } - - // 4a. If we have a py::args argument, create a new tuple with leftovers - if (func.has_args) { - if (positional_args_copied >= n_args_in) { - call.args_ref = tuple(0); - } else { - size_t args_size = n_args_in - positional_args_copied; - tuple extra_args(args_size); - for (size_t i = 0; i < args_size; ++i) { - extra_args[i] = args_in_arr[positional_args_copied + i]; - } - call.args_ref = std::move(extra_args); - } - if (call.args.size() <= func.nargs_pos) { - call.args.push_back(call.args_ref); - } else { - call.args[func.nargs_pos] = call.args_ref; - } - call.args_convert.push_back(false); - } - - // 4b. If we have a py::kwargs, pass on any remaining kwargs - if (func.has_kwargs) { - dict kwargs; - for (size_t i = 0; i < used_kwargs.size(); ++i) { - if (!used_kwargs[i]) { - // Cast values into handles before indexing into kwargs to ensure - // well-defined evaluation order (MSVC C4866). - handle arg_in_arr = args_in_arr[n_args_in + i], - kwname = PyTuple_GET_ITEM(kwnames_in, i); - kwargs[kwname] = arg_in_arr; - } - } - call.args.push_back(kwargs); - call.args_convert.push_back(false); - call.kwargs_ref = std::move(kwargs); - } - - // 5. Put everything in a vector. Not technically step 5, we've been building it - // in `call.args` all along. - -#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - if (call.args.size() != func.nargs || call.args_convert.size() != func.nargs) { - pybind11_fail("Internal error: function call dispatcher inserted wrong number " - "of arguments!"); - } -#endif - - args_convert_vector second_pass_convert; - if (overloaded) { - // We're in the first no-convert pass, so swap out the conversion flags for a - // set of all-false flags. If the call fails, we'll swap the flags back in for - // the conversion-allowed call below. - second_pass_convert = std::move(call.args_convert); - call.args_convert - = args_convert_vector(func.nargs, false); - } - - // 6. Call the function. - try { - loader_life_support guard{}; - result = func.impl(call); - } catch (reference_cast_error &) { - result = PYBIND11_TRY_NEXT_OVERLOAD; - } - - if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) { - break; - } - - if (overloaded) { - // The (overloaded) call failed; if the call has at least one argument that - // permits conversion (i.e. it hasn't been explicitly specified `.noconvert()`) - // then add this call to the list of second pass overloads to try. - for (size_t i = func.is_method ? 1 : 0; i < pos_args; i++) { - if (second_pass_convert[i]) { - // Found one: swap the converting flags back in and store the call for - // the second pass. - call.args_convert.swap(second_pass_convert); - second_pass.push_back(std::move(call)); - break; - } - } - } - } - - if (overloaded && !second_pass.empty() && result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) { - // The no-conversion pass finished without success, try again with conversion - // allowed - for (auto &call : second_pass) { - try { - loader_life_support guard{}; - result = call.func.impl(call); - } catch (reference_cast_error &) { - result = PYBIND11_TRY_NEXT_OVERLOAD; - } - - if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) { - // The error reporting logic below expects 'current_overload' to be valid, - // as it would be if we'd encountered this failure in the first-pass loop. - if (!result) { - current_overload = &call.func; - } - break; - } - } - } - } catch (error_already_set &e) { - e.restore(); - return nullptr; -#ifdef __GLIBCXX__ - } catch (abi::__forced_unwind &) { - throw; -#endif - } catch (...) { - try_translate_exceptions(); - return nullptr; - } - - auto append_note_if_missing_header_is_suspected = [](std::string &msg) { - if (msg.find("std::") != std::string::npos) { - msg += "\n\n" - "Did you forget to `#include `? Or ,\n" - ", , etc. Some automatic\n" - "conversions are optional and require extra headers to be included\n" - "when compiling your pybind11 module."; - } - }; - - if (result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) { - if (overloads->is_operator) { - return handle(Py_NotImplemented).inc_ref().ptr(); - } - - std::string msg = std::string(overloads->name) + "(): incompatible " - + std::string(overloads->is_constructor ? "constructor" : "function") - + " arguments. The following argument types are supported:\n"; - - int ctr = 0; - for (const function_record *it2 = overloads; it2 != nullptr; it2 = it2->next) { - msg += " " + std::to_string(++ctr) + ". "; - - bool wrote_sig = false; - if (overloads->is_constructor) { - // For a constructor, rewrite `(self: Object, arg0, ...) -> NoneType` as - // `Object(arg0, ...)` - std::string sig = it2->signature; - size_t start = sig.find('(') + 7; // skip "(self: " - if (start < sig.size()) { - // End at the , for the next argument - size_t end = sig.find(", "), next = end + 2; - size_t ret = sig.rfind(" -> "); - // Or the ), if there is no comma: - if (end >= sig.size()) { - next = end = sig.find(')'); - } - if (start < end && next < sig.size()) { - msg.append(sig, start, end - start); - msg += '('; - msg.append(sig, next, ret - next); - wrote_sig = true; - } - } - } - if (!wrote_sig) { - msg += it2->signature; - } - - msg += '\n'; - } - msg += "\nInvoked with: "; - bool some_args = false; - for (size_t ti = overloads->is_constructor ? 1 : 0; ti < n_args_in; ++ti) { - if (!some_args) { - some_args = true; - } else { - msg += ", "; - } - try { - msg += pybind11::repr(args_in_arr[ti]); - } catch (const error_already_set &) { - msg += ""; - } - } - if (kwnames_in && PyTuple_GET_SIZE(kwnames_in) > 0) { - if (some_args) { - msg += "; "; - } - msg += "kwargs: "; - bool first = true; - for (size_t i = 0; i < static_cast(PyTuple_GET_SIZE(kwnames_in)); ++i) { - if (first) { - first = false; - } else { - msg += ", "; - } - msg += reinterpret_borrow(PyTuple_GET_ITEM(kwnames_in, i)); - msg += '='; - try { - msg += pybind11::repr(args_in_arr[n_args_in + i]); - } catch (const error_already_set &) { - msg += ""; - } - } - } - - append_note_if_missing_header_is_suspected(msg); - // Attach additional error info to the exception if supported - if (PyErr_Occurred()) { - // #HelpAppreciated: unit test coverage for this branch. - raise_from(PyExc_TypeError, msg.c_str()); - return nullptr; - } - set_error(PyExc_TypeError, msg.c_str()); - return nullptr; - } - if (!result) { - std::string msg = "Unable to convert function return value to a " - "Python type! The signature was\n\t"; - assert(current_overload != nullptr); - msg += current_overload->signature; - append_note_if_missing_header_is_suspected(msg); - // Attach additional error info to the exception if supported - if (PyErr_Occurred()) { - raise_from(PyExc_TypeError, msg.c_str()); - return nullptr; - } - set_error(PyExc_TypeError, msg.c_str()); - return nullptr; - } - if (overloads->is_constructor && !self_value_and_holder.holder_constructed()) { - auto *pi = reinterpret_cast(parent.ptr()); - self_value_and_holder.type->init_instance(pi, nullptr); - } - return result.ptr(); - } + dispatcher(PyObject *self, PyObject *const *args_in_arr, size_t nargsf, PyObject *kwnames_in); static ssize_t keyword_index(PyObject *haystack, char const *needle) { /* kwargs is usually very small (<= 5 entries). The arg strings are typically interned. @@ -1437,17 +572,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods) // This implementation needs the definition of `class cpp_function`. -inline void tp_dealloc_impl(PyObject *self) { - // Save type before PyObject_Free invalidates self. - auto *type = Py_TYPE(self); - auto *py_func_rec = reinterpret_cast(self); - cpp_function::destruct(py_func_rec->cpp_func_rec); - py_func_rec->cpp_func_rec = nullptr; - // PyObject_New increments the heap type refcount and allocates via - // PyObject_Malloc; balance both here - PyObject_Free(self); - Py_DECREF(type); -} +void tp_dealloc_impl(PyObject *self); PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods) @@ -1541,52 +666,20 @@ inline void *multi_interp_slot(F &&, O &&...o) { Return a borrowed reference to the named module if it has been successfully initialized within this interpreter before. nullptr if it has not been successfully initialized. */ -inline PyObject *get_cached_module(pybind11::str const &nameobj) { - dict state = detail::get_python_state_dict(); - if (!state.contains("__pybind11_module_cache")) { - return nullptr; - } - dict cache = state["__pybind11_module_cache"]; - if (!cache.contains(nameobj)) { - return nullptr; - } - return cache[nameobj].ptr(); -} +PyObject *get_cached_module(pybind11::str const &nameobj); /* Add successfully initialized a module object to the internal cache. The module must have a __spec__ attribute with a name attribute. */ -inline void cache_completed_module(pybind11::object const &mod) { - dict state = detail::get_python_state_dict(); - if (!state.contains("__pybind11_module_cache")) { - state["__pybind11_module_cache"] = dict(); - } - state["__pybind11_module_cache"][mod.attr("__spec__").attr("name")] = mod; -} +void cache_completed_module(pybind11::object const &mod); /* A Py_mod_create slot function which will return the previously created module from the cache if one exists, and otherwise will create a new module object. */ -inline PyObject *cached_create_module(PyObject *spec, PyModuleDef *) { - (void) &cache_completed_module; // silence unused-function warnings, it is used in a macro - - auto nameobj = getattr(reinterpret_borrow(spec), "name", none()); - if (nameobj.is_none()) { - set_error(PyExc_ImportError, "module spec is missing a name"); - return nullptr; - } - - auto *mod = get_cached_module(nameobj); - if (mod) { - Py_INCREF(mod); - } else { - mod = PyModule_NewObject(nameobj.ptr()); - } - return mod; -} +PyObject *cached_create_module(PyObject *spec, PyModuleDef *); /// Must be a POD type, and must hold enough entries for all of the possible slots PLUS ONE for /// the sentinel (0) end slot. @@ -1793,16 +886,7 @@ using module = module_; /// \ingroup python_builtins /// Return a dictionary representing the global variables in the current execution frame, /// or ``__main__.__dict__`` if there is no frame (usually when the interpreter is embedded). -inline dict globals() { -#if PY_VERSION_HEX >= 0x030d0000 - PyObject *p = PyEval_GetFrameGlobals(); - return p ? reinterpret_steal(p) - : reinterpret_borrow(module_::import("__main__").attr("__dict__").ptr()); -#else - PyObject *p = PyEval_GetGlobals(); - return reinterpret_borrow(p ? p : module_::import("__main__").attr("__dict__").ptr()); -#endif -} +dict globals(); PYBIND11_NAMESPACE_BEGIN(detail) /// Generic support for creating new Python heap types @@ -1810,93 +894,10 @@ class generic_type : public object { public: PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check) protected: - void initialize(const type_record &rec) { - if (rec.scope && hasattr(rec.scope, "__dict__") - && rec.scope.attr("__dict__").contains(rec.name)) { - pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) - + "\": an object with that name is already defined"); - } - - if ((rec.module_local ? get_local_type_info(*rec.type) : get_global_type_info(*rec.type)) - != nullptr) { - pybind11_fail("generic_type: type \"" + std::string(rec.name) - + "\" is already registered!"); - } - - m_ptr = make_new_python_type(rec); - - /* Register supplemental type information in C++ dict */ - auto *tinfo = new detail::type_info(); - tinfo->type = reinterpret_cast(m_ptr); - tinfo->cpptype = rec.type; - tinfo->type_size = rec.type_size; - tinfo->type_align = rec.type_align; - tinfo->operator_new = rec.operator_new; - tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size); - tinfo->init_instance = rec.init_instance; - tinfo->dealloc = rec.dealloc; - tinfo->get_trampoline_self_life_support = rec.get_trampoline_self_life_support; - tinfo->simple_type = true; - tinfo->simple_ancestors = true; - tinfo->module_local = rec.module_local; - tinfo->holder_enum_v = rec.holder_enum_v; - - 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) { - 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 -#if defined(__GNUC__) && __GNUC__ == 12 - // When using GCC 12 these warnings are disabled as they trigger - // false positive warnings. Discussed here: - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115824. - PYBIND11_WARNING_DISABLE_GCC("-Warray-bounds") - PYBIND11_WARNING_DISABLE_GCC("-Wstringop-overread") -#endif - internals.registered_types_py[reinterpret_cast(m_ptr)] = {tinfo}; - PYBIND11_WARNING_POP - }); - - if (rec.bases.size() > 1 || rec.multiple_inheritance) { - mark_parents_nonsimple(tinfo->type); - tinfo->simple_ancestors = false; - } else if (rec.bases.size() == 1) { - auto *parent_tinfo - = get_type_info(reinterpret_cast(rec.bases[0].ptr())); - assert(parent_tinfo != nullptr); - bool parent_simple_ancestors = parent_tinfo->simple_ancestors; - tinfo->simple_ancestors = parent_simple_ancestors; - // The parent can no longer be a simple type if it has MI and has a child - parent_tinfo->simple_type = parent_tinfo->simple_type && parent_simple_ancestors; - } - - if (rec.module_local) { - // Stash the local typeinfo and loader so that external modules can access it. - tinfo->module_local_load = &type_caster_generic::local_load; - setattr(m_ptr, PYBIND11_MODULE_LOCAL_ID, capsule(tinfo)); - } - } + void initialize(const type_record &rec); /// Helper function which tags all parents of a type using mult. inheritance - void mark_parents_nonsimple(PyTypeObject *value) { - auto t = reinterpret_borrow(value->tp_bases); - for (handle h : t) { - auto *tinfo2 = get_type_info(reinterpret_cast(h.ptr())); - if (tinfo2) { - tinfo2->simple_type = false; - } - mark_parents_nonsimple(reinterpret_cast(h.ptr())); - } - } + void mark_parents_nonsimple(PyTypeObject *value); void install_buffer_funcs(buffer_info *(*get_buffer)(PyObject *, void *), void *get_buffer_data) { @@ -1965,32 +966,9 @@ void call_operator_delete(T *p, size_t s, size_t) { T::operator delete(p, s); } -inline void call_operator_delete(void *p, size_t s, size_t a) { - (void) s; - (void) a; -#if defined(__cpp_aligned_new) - if (a > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { -# ifdef __cpp_sized_deallocation - ::operator delete(p, s, std::align_val_t(a)); -# else - ::operator delete(p, std::align_val_t(a)); -# endif - return; - } -#endif -#ifdef __cpp_sized_deallocation - ::operator delete(p, s); -#else - ::operator delete(p); -#endif -} +void call_operator_delete(void *p, size_t s, size_t a); -inline void add_class_method(object &cls, const char *name_, const cpp_function &cf) { - cls.attr(cf.name()) = cf; - if (std::strcmp(name_, "__eq__") == 0 && !cls.attr("__dict__").contains("__hash__")) { - cls.attr("__hash__") = none(); - } -} +void add_class_method(object &cls, const char *name_, const cpp_function &cf); /// Type trait to rebind a member function pointer's class to `Derived`, preserving all /// cv/ref/noexcept qualifiers. The primary template has no `type` member, providing SFINAE @@ -2965,170 +1943,16 @@ detail::initimpl::pickle_factory pickle(GetState &&g, SetSta PYBIND11_NAMESPACE_BEGIN(detail) -inline str enum_name(handle arg) { - dict entries = type::handle_of(arg).attr("__entries"); - for (auto kv : entries) { - if (handle(kv.second[int_(0)]).equal(arg)) { - return pybind11::str(kv.first); - } - } - return "???"; -} +str enum_name(handle arg); struct enum_base { enum_base(const handle &base, const handle &parent) : m_base(base), m_parent(parent) {} - PYBIND11_NOINLINE void init(bool is_arithmetic, bool is_convertible) { - m_base.attr("__entries") = dict(); - auto property = handle(reinterpret_cast(&PyProperty_Type)); - auto static_property - = handle(reinterpret_cast(get_internals().static_property_type)); - - m_base.attr("__repr__") = cpp_function( - [](const object &arg) -> str { - handle type = type::handle_of(arg); - object type_name = type.attr("__name__"); - return pybind11::str("<{}.{}: {}>") - .format(std::move(type_name), enum_name(arg), int_(arg)); - }, - name("__repr__"), - is_method(m_base), - pos_only()); + void init(bool is_arithmetic, bool is_convertible); - m_base.attr("name") - = property(cpp_function(&enum_name, name("name"), is_method(m_base), pos_only())); + void value(char const *name_, object value, const char *doc = nullptr); - m_base.attr("__str__") = cpp_function( - [](handle arg) -> str { - object type_name = type::handle_of(arg).attr("__name__"); - return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg)); - }, - name("__str__"), - is_method(m_base), - pos_only()); - - if (options::show_enum_members_docstring()) { - m_base.attr("__doc__") = static_property( - cpp_function( - [](handle arg) -> std::string { - std::string docstring; - dict entries = arg.attr("__entries"); - if ((reinterpret_cast(arg.ptr()))->tp_doc) { - docstring += std::string( - reinterpret_cast(arg.ptr())->tp_doc); - docstring += "\n\n"; - } - docstring += "Members:"; - for (auto kv : entries) { - auto key = std::string(pybind11::str(kv.first)); - auto comment = kv.second[int_(1)]; - docstring += "\n\n "; - docstring += key; - if (!comment.is_none()) { - docstring += " : "; - docstring += pybind11::str(comment).cast(); - } - } - return docstring; - }, - name("__doc__")), - none(), - none(), - ""); - } - - m_base.attr("__members__") = static_property(cpp_function( - [](handle arg) -> dict { - dict entries = arg.attr("__entries"), - m; - for (auto kv : entries) { - m[kv.first] = kv.second[int_(0)]; - } - return m; - }, - name("__members__")), - none(), - none(), - ""); - -#define PYBIND11_ENUM_OP_STRICT(op, expr, strict_behavior) \ - m_base.attr(op) = cpp_function( \ - [](const object &a, const object &b) { \ - if (!type::handle_of(a).is(type::handle_of(b))) \ - strict_behavior; /* NOLINT(bugprone-macro-parentheses) */ \ - return expr; \ - }, \ - name(op), \ - is_method(m_base), \ - arg("other"), \ - pos_only()) - -#define PYBIND11_ENUM_OP_CONV(op, expr) \ - m_base.attr(op) = cpp_function( \ - [](const object &a_, const object &b_) { \ - int_ a(a_), b(b_); \ - return expr; \ - }, \ - name(op), \ - is_method(m_base), \ - arg("other"), \ - pos_only()) - -#define PYBIND11_ENUM_OP_CONV_LHS(op, expr) \ - m_base.attr(op) = cpp_function( \ - [](const object &a_, const object &b) { \ - int_ a(a_); \ - return expr; \ - }, \ - name(op), \ - is_method(m_base), \ - arg("other"), \ - pos_only()) - - if (is_convertible) { - if (is_arithmetic) { - m_base.attr("__invert__") - = cpp_function([](const object &arg) { return ~(int_(arg)); }, - name("__invert__"), - is_method(m_base), - pos_only()); - } - } - -#undef PYBIND11_ENUM_OP_CONV_LHS -#undef PYBIND11_ENUM_OP_CONV -#undef PYBIND11_ENUM_OP_STRICT - - m_base.attr("__getstate__") = cpp_function([](const object &arg) { return int_(arg); }, - name("__getstate__"), - is_method(m_base), - pos_only()); - - m_base.attr("__hash__") = cpp_function([](const object &arg) { return int_(arg); }, - name("__hash__"), - is_method(m_base), - pos_only()); - } - - PYBIND11_NOINLINE void value(char const *name_, object value, const char *doc = nullptr) { - dict entries = m_base.attr("__entries"); - str name(name_); - if (entries.contains(name)) { - std::string type_name = std::string(str(m_base.attr("__name__"))); - throw value_error(std::move(type_name) + ": element \"" + std::string(name_) - + "\" already exists!"); - } - - entries[name] = pybind11::make_tuple(value, doc); - m_base.attr(std::move(name)) = std::move(value); - } - - PYBIND11_NOINLINE void export_values() { - dict entries = m_base.attr("__entries"); - for (auto kv : entries) { - m_parent.attr(kv.first) = kv.second[int_(0)]; - } - } + void export_values(); handle m_base; handle m_parent; @@ -3324,97 +2148,12 @@ class enum_ : public class_ { PYBIND11_NAMESPACE_BEGIN(detail) -PYBIND11_NOINLINE void keep_alive_impl(handle nurse, handle patient) { - if (!nurse || !patient) { - pybind11_fail("Could not activate keep_alive!"); - } - - if (patient.is_none() || nurse.is_none()) { - return; /* Nothing to keep alive or nothing to be kept alive by */ - } +void keep_alive_impl(handle nurse, handle patient); - auto tinfo = all_type_info(Py_TYPE(nurse.ptr())); - if (!tinfo.empty()) { - /* It's a pybind-registered type, so we can store the patient in the - * internal list. */ - add_patient(nurse.ptr(), patient.ptr()); - } else { - /* Fall back to clever approach based on weak references taken from - * Boost.Python. This is not used for pybind-registered types because - * the objects can be destroyed out-of-order in a GC pass. */ - cpp_function disable_lifesupport([patient](handle weakref) { - patient.dec_ref(); - weakref.dec_ref(); - }); +void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret); - weakref wr(nurse, disable_lifesupport); - - patient.inc_ref(); /* reference patient and leak the weak reference */ - (void) wr.release(); - } -} - -PYBIND11_NOINLINE void -keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret) { - auto get_arg = [&](size_t n) { - if (n == 0) { - return ret; - } - if (n == 1 && call.init_self) { - return call.init_self; - } - if (n <= call.args.size()) { - return call.args[n - 1]; - } - return handle(); - }; - - keep_alive_impl(get_arg(Nurse), get_arg(Patient)); -} - -inline std::pair -all_type_info_get_cache(PyTypeObject *type) { - auto res = with_internals([type](internals &internals) { - auto ins = internals - .registered_types_py -#ifdef __cpp_lib_unordered_map_try_emplace - .try_emplace(type); -#else - .emplace(type, std::vector()); -#endif - if (ins.second) { - // For free-threading mode, this call must be under - // the with_internals() mutex lock, to avoid that other threads - // continue running with the empty ins.first->second. - all_type_info_populate(type, ins.first->second); - } - return ins; - }); - if (res.second) { - // New cache entry created; set up a weak reference to automatically remove it if the type - // gets destroyed: - weakref(reinterpret_cast(type), cpp_function([type](handle wr) { - with_internals([type](internals &internals) { - internals.registered_types_py.erase(type); - - // TODO consolidate the erasure code in pybind11_meta_dealloc() in class.h - auto &cache = internals.inactive_override_cache; - for (auto it = cache.begin(), last = cache.end(); it != last;) { - if (it->first == reinterpret_cast(type)) { - it = cache.erase(it); - } else { - ++it; - } - } - }); - - wr.dec_ref(); - })) - .release(); - } - - return res; -} +std::pair +all_type_info_get_cache(PyTypeObject *type); /* There are a large number of apparently unused template arguments because * each combination requires a separate py::class_ registration. @@ -3650,14 +2389,7 @@ void implicitly_convertible() { } } -inline void register_exception_translator(ExceptionTranslator &&translator) { - detail::with_exception_translators( - [&](std::forward_list &exception_translators, - std::forward_list &local_exception_translators) { - (void) local_exception_translators; - exception_translators.push_front(std::forward(translator)); - }); -} +void register_exception_translator(ExceptionTranslator &&translator); /** * Add a new module-local exception translator. Locally registered functions @@ -3665,14 +2397,7 @@ inline void register_exception_translator(ExceptionTranslator &&translator) { * will only be invoked if the module-local handlers do not deal with * the exception. */ -inline void register_local_exception_translator(ExceptionTranslator &&translator) { - detail::with_exception_translators( - [&](std::forward_list &exception_translators, - std::forward_list &local_exception_translators) { - (void) exception_translators; - local_exception_translators.push_front(std::forward(translator)); - }); -} +void register_local_exception_translator(ExceptionTranslator &&translator); /** * Wrapper to generate a new Python exception type. @@ -3763,23 +2488,7 @@ register_local_exception(handle scope, const char *name, handle base = PyExc_Exc } PYBIND11_NAMESPACE_BEGIN(detail) -PYBIND11_NOINLINE void print(const tuple &args, const dict &kwargs) { -#if PY_VERSION_HEX >= 0x030D0000 - auto builtins = reinterpret_steal(PyEval_GetFrameBuiltins()); -#else - auto builtins = reinterpret_borrow(PyEval_GetBuiltins()); -#endif - // The builtins dictionary may already be partially cleared during interpreter shutdown. - auto native_print = reinterpret_steal(dict_getitemstringref(builtins.ptr(), "print")); - if (!native_print) { - return; - } - auto result - = reinterpret_steal(PyObject_Call(native_print.ptr(), args.ptr(), kwargs.ptr())); - if (!result) { - throw error_already_set(); - } -} +void print(const tuple &args, const dict &kwargs); PYBIND11_NAMESPACE_END(detail) template @@ -3788,112 +2497,12 @@ void print(Args &&...args) { detail::print(c.args(), c.kwargs()); } -inline void -error_already_set::m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr) { - gil_scoped_acquire gil; - error_scope scope; - delete raw_ptr; -} - -inline const char *error_already_set::what() const noexcept { - gil_scoped_acquire gil; - error_scope scope; - return m_fetched_error->error_string().c_str(); -} +// (error_already_set::m_fetched_error_deleter and ::what are declared in pytypes.h; +// definitions are in pybind11-inl.h.) PYBIND11_NAMESPACE_BEGIN(detail) -inline function -get_type_override(const void *this_ptr, const type_info *this_type, const char *name) { - handle self = get_object_handle(this_ptr, this_type); - if (!self) { - return function(); - } - handle type = type::handle_of(self); - auto key = std::make_pair(type.ptr(), name); - - /* Cache functions that aren't overridden in Python to avoid - many costly Python dictionary lookups below */ - bool not_overridden = with_internals([&key](internals &internals) { - auto &cache = internals.inactive_override_cache; - return cache.find(key) != cache.end(); - }); - if (not_overridden) { - return function(); - } - - function override = getattr(self, name, function()); - if (override.is_cpp_function()) { - with_internals([&](internals &internals) { - internals.inactive_override_cache.insert(std::move(key)); - }); - return function(); - } - - /* Don't call dispatch code if invoked from overridden function. - Unfortunately this doesn't work on PyPy and GraalPy. */ -#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) - PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get()); - if (frame != nullptr) { - PyCodeObject *f_code = PyFrame_GetCode(frame); - // f_code is guaranteed to not be NULL - if (std::string(str(f_code->co_name)) == name && f_code->co_argcount > 0) { -# if PY_VERSION_HEX >= 0x030d0000 - PyObject *locals = PyEval_GetFrameLocals(); -# else - PyObject *locals = PyEval_GetLocals(); - Py_XINCREF(locals); -# endif - if (locals != nullptr) { -# if PY_VERSION_HEX >= 0x030b0000 - PyObject *co_varnames = PyCode_GetVarnames(f_code); -# else - PyObject *co_varnames = PyObject_GetAttrString((PyObject *) f_code, "co_varnames"); -# endif - PyObject *self_arg = PyTuple_GET_ITEM(co_varnames, 0); - Py_DECREF(co_varnames); - PyObject *self_caller = dict_getitem(locals, self_arg); - Py_DECREF(locals); - if (self_caller == self.ptr()) { - Py_DECREF(f_code); - Py_DECREF(frame); - return function(); - } - } - } - Py_DECREF(f_code); - Py_DECREF(frame); - } - -#else - /* PyPy currently doesn't provide a detailed cpyext emulation of - frame objects, so we have to emulate this using Python. This - is going to be slow..*/ - dict d; - d["self"] = self; - d["name"] = pybind11::str(name); - PyObject *result - = PyRun_String("import inspect\n" - "frame = inspect.currentframe()\n" - "if frame is not None:\n" - " frame = frame.f_back\n" - " if frame is not None and str(frame.f_code.co_name) == name and " - "frame.f_code.co_argcount > 0:\n" - " self_caller = frame.f_locals[frame.f_code.co_varnames[0]]\n" - " if self_caller == self:\n" - " self = None\n", - Py_file_input, - d.ptr(), - d.ptr()); - if (result == nullptr) - throw error_already_set(); - Py_DECREF(result); - if (d["self"].is_none()) - return function(); -#endif - - return override; -} +function get_type_override(const void *this_ptr, const type_info *this_type, const char *name); PYBIND11_NAMESPACE_END(detail) /** \rst @@ -4029,3 +2638,7 @@ inline function get_overload(const T *this_ptr, const char *name) { PYBIND11_OVERRIDE_PURE(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), fn, __VA_ARGS__); PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_PRECOMPILED +# include "pybind11-inl.h" // IWYU pragma: export +#endif diff --git a/src/pybind11.cpp b/src/pybind11.cpp new file mode 100644 index 0000000000..63ac0243dd --- /dev/null +++ b/src/pybind11.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 cc272ab5f5..cdeaf9d32e 100644 --- a/src/pybind11_combined.cpp +++ b/src/pybind11_combined.cpp @@ -4,17 +4,17 @@ // Single-TU build of the pybind11 library sources, for build systems that prefer adding // one file over one file per header (e.g. setuptools). Compile this file (and every TU -// that includes pybind11) with PYBIND11_PRECOMPILED defined. Keep in sync with the list -// of -inl.h files; the CMake path compiles the individual src/*.cpp files instead. +// that includes pybind11) with PYBIND11_PRECOMPILED defined. One include per sibling +// src/*.cpp file; the CMake path compiles those files individually instead. #if !defined(PYBIND11_PRECOMPILED) # error "pybind11 library sources must be compiled with PYBIND11_PRECOMPILED defined." #endif -#include -#include -#include -#include -#include -#include -#include +#include "class.cpp" +#include "common.cpp" +#include "exception_translation.cpp" +#include "internals.cpp" +#include "pybind11.cpp" +#include "pytypes.cpp" +#include "type_caster_base.cpp" diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index b43a5cbdb9..58dbca4bc1 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -61,6 +61,7 @@ "include/pybind11/numpy.h", "include/pybind11/operators.h", "include/pybind11/options.h", + "include/pybind11/pybind11-inl.h", "include/pybind11/pybind11.h", "include/pybind11/pytypes-inl.h", "include/pybind11/pytypes.h", @@ -138,6 +139,7 @@ "src/exception_translation.cpp", "src/internals.cpp", "src/type_caster_base.cpp", + "src/pybind11.cpp", "src/pybind11_combined.cpp", "src/pytypes.cpp", } From b8d7622e4509f8f305dfc6290e32419c25a38a8a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 7 Aug 2026 17:29:29 -0400 Subject: [PATCH 12/14] fix: drop redundant redeclarations in pybind11.h These functions are already declared in function_record_pyobject.h, attr.h, and type_caster_base.h; GCC 13 -Wredundant-decls rejects the repeats. Assisted-by: ClaudeCode:claude-fable-5 --- include/pybind11/pybind11.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 7126cb501c..434d6f8829 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -569,13 +569,6 @@ class cpp_function : public function { PYBIND11_NAMESPACE_BEGIN(detail) -PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods) - -// This implementation needs the definition of `class cpp_function`. -void tp_dealloc_impl(PyObject *self); - -PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods) - template <> struct handle_type_name { static constexpr auto name = const_name("collections.abc.Callable"); @@ -2148,13 +2141,6 @@ class enum_ : public class_ { PYBIND11_NAMESPACE_BEGIN(detail) -void keep_alive_impl(handle nurse, handle patient); - -void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret); - -std::pair -all_type_info_get_cache(PyTypeObject *type); - /* There are a large number of apparently unused template arguments because * each combination requires a separate py::class_ registration. */ From 1d3436ada08b549ec6f9cf3dbd93b8a531bd04cd Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 23:20:07 -0400 Subject: [PATCH 13/14] feat: expose the precompiled-mode sources to non-CMake builds Adds pybind11.get_source_dir() / python -m pybind11 --srcdir and a srcdir variable in pybind11.pc, so build systems such as Meson can compile src/pybind11_combined.cpp with PYBIND11_PRECOMPILED defined. Assisted-by: ClaudeCode:claude-fable-5 --- CMakeLists.txt | 9 +++++---- pybind11/__init__.py | 3 ++- pybind11/__main__.py | 9 +++++++++ pybind11/commands.py | 18 ++++++++++++++++++ tests/extra_python_package/test_files.py | 1 + tools/pybind11.pc.in | 1 + 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea1b3a157b..f53a646b67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -343,8 +343,8 @@ if(PYBIND11_INSTALL) install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 DESTINATION "${SKBUILD_HEADERS_DIR}") endif() install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pybind11/src") + set(pybind11_install_srcdir "${CMAKE_INSTALL_DATAROOTDIR}/pybind11/src") + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ DESTINATION "${pybind11_install_srcdir}") set(PYBIND11_CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}" CACHE STRING "install path for pybind11Config.cmake") @@ -355,9 +355,9 @@ if(PYBIND11_INSTALL) set(pybind11_INCLUDEDIR "\$\{PACKAGE_PREFIX_DIR\}/${CMAKE_INSTALL_INCLUDEDIR}") endif() if(IS_ABSOLUTE "${CMAKE_INSTALL_DATAROOTDIR}") - set(pybind11_SRCDIR "${CMAKE_INSTALL_DATAROOTDIR}/pybind11/src") + set(pybind11_SRCDIR "${pybind11_install_srcdir}") else() - set(pybind11_SRCDIR "\$\{PACKAGE_PREFIX_DIR\}/${CMAKE_INSTALL_DATAROOTDIR}/pybind11/src") + set(pybind11_SRCDIR "\$\{PACKAGE_PREFIX_DIR\}/${pybind11_install_srcdir}") endif() configure_package_config_file( @@ -410,6 +410,7 @@ if(PYBIND11_INSTALL) endif() endif() join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") + join_paths(srcdir_for_pc_file "\${prefix}" "${pybind11_install_srcdir}") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" diff --git a/pybind11/__init__.py b/pybind11/__init__.py index 3882b2b17b..1d66e119d6 100644 --- a/pybind11/__init__.py +++ b/pybind11/__init__.py @@ -8,12 +8,13 @@ from ._version import __version__, version_info -from .commands import get_cmake_dir, get_include, get_pkgconfig_dir +from .commands import get_cmake_dir, get_include, get_pkgconfig_dir, get_source_dir __all__ = ( "__version__", "get_cmake_dir", "get_include", "get_pkgconfig_dir", + "get_source_dir", "version_info", ) diff --git a/pybind11/__main__.py b/pybind11/__main__.py index ce597c781a..98e9184807 100644 --- a/pybind11/__main__.py +++ b/pybind11/__main__.py @@ -17,6 +17,7 @@ get_include_dirs, get_ldflags, get_pkgconfig_dir, + get_source_dir, ) @@ -50,6 +51,12 @@ def main() -> None: action="store_true", help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.", ) + parser.add_argument( + "--srcdir", + action="store_true", + help="Print the directory containing the library sources for the optional" + " precompiled mode.", + ) parser.add_argument( "--extension-suffix", action="store_true", @@ -101,6 +108,8 @@ def main() -> None: print(quote(get_cmake_dir())) if args.pkgconfigdir: print(quote(get_pkgconfig_dir())) + if args.srcdir: + print(quote(get_source_dir())) if args.extension_suffix: print(ext_suffix) diff --git a/pybind11/commands.py b/pybind11/commands.py index 8bd0a9bf13..573b7cf4ac 100644 --- a/pybind11/commands.py +++ b/pybind11/commands.py @@ -52,6 +52,24 @@ def get_include(user: bool = False) -> str: # noqa: ARG001 return installed_path if os.path.exists(installed_path) else source_path +def get_source_dir() -> str: + """ + Return the path to the pybind11 library sources, for the optional + precompiled mode. Compile ``pybind11_combined.cpp`` (or the individual + ``.cpp`` files) with ``PYBIND11_PRECOMPILED`` defined, and define that + macro for every translation unit that includes pybind11. + """ + installed_path = os.path.join(DIR, "share", "pybind11", "src") + source_path = os.path.join(os.path.dirname(DIR), "src") + if os.path.exists(installed_path): + return installed_path + if os.path.exists(source_path): + return source_path + + msg = "pybind11 library sources not found (pybind11 not installed?)" + raise ImportError(msg) + + def get_cmake_dir() -> str: """ Return the path to the pybind11 CMake module directory. diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 58dbca4bc1..a14f057a68 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -33,6 +33,7 @@ PKGCONFIG = """\ prefix=${{pcfiledir}}/../../ includedir=${{prefix}}/include +srcdir=${{prefix}}/share/pybind11/src Name: pybind11 Description: Seamless operability between C++11 and Python diff --git a/tools/pybind11.pc.in b/tools/pybind11.pc.in index 402f0b357d..8b5af0a991 100644 --- a/tools/pybind11.pc.in +++ b/tools/pybind11.pc.in @@ -1,5 +1,6 @@ prefix=@prefix_for_pc_file@ includedir=@includedir_for_pc_file@ +srcdir=@srcdir_for_pc_file@ Name: @PROJECT_NAME@ Description: Seamless operability between C++11 and Python From 6615f305519056f3bb5abcddf618c5933b8606da Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 23:21:44 -0400 Subject: [PATCH 14/14] docs: document the opt-in precompiled mode Assisted-by: ClaudeCode:claude-fable-5 --- docs/compiling.rst | 95 ++++++++++++++++++++++++++++++++++- docs/faq.rst | 7 ++- tools/pybind11Config.cmake.in | 20 ++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/docs/compiling.rst b/docs/compiling.rst index a6bee86ffe..a9c9fcf17e 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -348,7 +348,8 @@ function with the following signature: .. code-block:: cmake pybind11_add_module( [MODULE | SHARED] [EXCLUDE_FROM_ALL] - [NO_EXTRAS] [THIN_LTO] [OPT_SIZE] source1 [source2 ...]) + [NO_EXTRAS] [THIN_LTO] [OPT_SIZE] [PRECOMPILE | NO_PRECOMPILE] + source1 [source2 ...]) This function behaves very much like CMake's builtin ``add_library`` (in fact, it's a wrapper function around that command). It will add a library target @@ -404,6 +405,98 @@ optimizations remain disabled. .. _ThinLTO: http://clang.llvm.org/docs/ThinLTO.html +.. _precompile-mode: + +Pre-compiling part of pybind11 +------------------------------ + +pybind11 is header-only by default: every translation unit compiles its own +copy of the non-template implementation. The opt-in *precompiled* mode +compiles that implementation once, into a static library built inside your +own project with your own flags. This reduces the build time, most of all for +projects with many translation units or many modules in one build. + +.. code-block:: cmake + + pybind11_add_module(example PRECOMPILE example.cpp) + +The first ``PRECOMPILE`` target creates the library target +``pybind11::precompiled``; further targets reuse it. Set the CMake variable +``PYBIND11_PRECOMPILE`` to make it the default for all +``pybind11_add_module`` calls; use ``NO_PRECOMPILE`` on a target to opt back +out. For targets you create yourself, call the ``pybind11_precompile()`` +function and link ``pybind11::precompiled`` PRIVATE; the target carries the +required ``PYBIND11_PRECOMPILED`` compile definition PUBLIC, so your sources +also get it. + +Requirements and caveats: + +* The library and every module linking it must agree on the configuration + macros ``PYBIND11_INTERNALS_VERSION``, ``Py_GIL_DISABLED``, + ``PYBIND11_SIMPLE_GIL_MANAGEMENT``, + ``PYBIND11_DETAILED_ERROR_MESSAGES`` (defaults on in debug builds), + ``PYBIND11_HAS_SUBINTERPRETER_SUPPORT``, and + ``PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET``. A mismatch produces one + readable undefined symbol at link time referencing + ``pybind11_precompiled_config``. +* Configuration macros that only change code inside the library (for example + ``PYBIND11_DISABLE_NEW_STYLE_INIT_WARNING``) must be defined when the + library is compiled; a definition only on your module has no effect. +* The library picks up your directory-level flags and C++ standard when it is + first created, so set those before the first ``PRECOMPILE`` target. A + status message reports the directory that created the library. +* The library is not compiled with link-time optimization, and the per-target + ``THIN_LTO`` and ``OPT_SIZE`` options of ``pybind11_add_module`` do not + apply to it. To change this, call ``pybind11_precompile()`` yourself and + set the properties on the created target, ``pybind11_precompiled`` (the + real target behind the ``pybind11::precompiled`` alias; CMake does not let + you set properties through an alias): + + .. code-block:: cmake + + pybind11_precompile() + set_target_properties(pybind11_precompiled PROPERTIES + INTERPROCEDURAL_OPTIMIZATION ON) + +* The library is static and per-build-tree; it is never installed or shared + between projects. Each extension module links its own copy, which keeps + pybind11's per-module state the same as in header-only mode. +* Not available with ``PYBIND11_NOPYTHON`` (the library needs Python + headers). + +For build systems other than CMake, the same sources ship with the pybind11 +package: compile ``pybind11_combined.cpp`` from the directory reported by +``python -m pybind11 --srcdir`` (also available as +``pybind11.get_source_dir()`` and the ``srcdir`` pkg-config variable) into +a static library or into your extension, and define +``PYBIND11_PRECOMPILED`` for every translation unit. + +With Meson, build the library once per build tree and link it into each +extension module, the same as the CMake path: + +.. code-block:: meson + + pybind11_dep = dependency('pybind11') + pybind11_src = run_command(py, ['-m', 'pybind11', '--srcdir'], + check : true).stdout().strip() + + pybind11_precompiled = static_library('pybind11_precompiled', + pybind11_src / 'pybind11_combined.cpp', + cpp_args : ['-DPYBIND11_PRECOMPILED'], + gnu_symbol_visibility : 'hidden', + dependencies : [pybind11_dep, py.dependency()]) + + py.extension_module('example', 'example.cpp', + cpp_args : ['-DPYBIND11_PRECOMPILED'], + link_with : pybind11_precompiled, + dependencies : [pybind11_dep]) + +The configuration-macro rules above apply here too: the static library and +every module that links it must be compiled with the same configuration +macros, and ``-DPYBIND11_PRECOMPILED`` must appear in both ``cpp_args`` +lists. (``pybind11_dep.get_variable('srcdir')`` also reports the source +directory when Meson finds pybind11 through pkg-config.) + Configuration variables ----------------------- diff --git a/docs/faq.rst b/docs/faq.rst index 2b89d203f3..8a061c812d 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -79,7 +79,12 @@ and the binding code How can I reduce the build time? ================================ -It's good practice to split binding code over multiple files, as in the +First, consider the opt-in precompiled mode: it compiles the non-template +part of pybind11 once per project instead of once for each translation unit. +In CMake, this is one keyword on ``pybind11_add_module``. See +:ref:`precompile-mode`. + +It's also good practice to split binding code over multiple files, as in the following example: :file:`example.cpp`: diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index abcd43e199..d666e9cc96 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -18,6 +18,9 @@ This module sets the following variables in your project: Directories where pybind11 and python headers are located. ``pybind11_INCLUDE_DIR`` Directory where pybind11 headers are located. +``pybind11_SRC_DIR`` + Directory where the library sources for the opt-in precompiled mode are + located (used by ``pybind11_precompile``). ``pybind11_DEFINITIONS`` Definitions necessary to use pybind11, namely USING_pybind11. ``pybind11_LIBRARIES`` @@ -147,6 +150,7 @@ This module defines the following commands to assist with creating Python module pybind11_add_module( [STATIC|SHARED|MODULE] [THIN_LTO] [OPT_SIZE] [NO_EXTRAS] [WITHOUT_SOABI] + [PRECOMPILE|NO_PRECOMPILE] ... ) @@ -162,6 +166,22 @@ default is ``MODULE``. There are several options: Disable the SOABI component (``PYBIND11_FINDPYTHON`` mode only). ``NO_EXTRAS`` Disable all extras, exit immediately after making the module. +``PRECOMPILE`` + Link the target against the ``pybind11::precompiled`` static library + (created on first use); ``NO_PRECOMPILE`` opts a target out when the + ``PYBIND11_PRECOMPILE`` variable enables it globally. + +pybind11_precompile +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: cmake + + pybind11_precompile() + +Create the ``pybind11::precompiled`` static library from the shipped sources +(once per build tree). ``pybind11_add_module(... PRECOMPILE)`` calls this for +you; call it directly to link ``pybind11::precompiled`` into your own +targets. pybind11_strip ^^^^^^^^^^^^^^