Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: start pybind11v3: Remove all code for PYBIND11_INTERNALS_VERSIONs 4 and 5 #5530

Merged
merged 5 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
# error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7."
#endif

#define PYBIND11_VERSION_MAJOR 2
#define PYBIND11_VERSION_MINOR 14
#define PYBIND11_VERSION_MAJOR 3
#define PYBIND11_VERSION_MINOR 0
#define PYBIND11_VERSION_PATCH 0.dev1

// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
// Additional convention: 0xD = dev
#define PYBIND11_VERSION_HEX 0x020E00D1
#define PYBIND11_VERSION_HEX 0x030000D1

// Define some generic pybind11 helper macros for warning management.
//
Expand Down
118 changes: 30 additions & 88 deletions include/pybind11/detail/internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,9 @@
# define PYBIND11_INTERNALS_VERSION 6
#endif

// This requirement is mainly to reduce the support burden (see PR #4570).
static_assert(PY_VERSION_HEX < 0x030C0000 || PYBIND11_INTERNALS_VERSION >= 5,
"pybind11 ABI version 5 is the minimum for Python 3.12+");
static_assert(PYBIND11_INTERNALS_VERSION >= 4,
"pybind11 ABI version 4 is the minimum for all platforms.");
#if PYBIND11_INTERNALS_VERSION < 6
# error "PYBIND11_INTERNALS_VERSION 6 is the minimum for all platforms for pybind11v3."
#endif

PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)

Expand All @@ -63,49 +61,37 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass);
// Thread Specific Storage (TSS) API.
// Avoid unnecessary allocation of `Py_tss_t`, since we cannot use
// `Py_LIMITED_API` anyway.
#if PYBIND11_INTERNALS_VERSION > 4
# define PYBIND11_TLS_KEY_REF Py_tss_t &
# if defined(__clang__)
# define PYBIND11_TLS_KEY_INIT(var) \
_Pragma("clang diagnostic push") /**/ \
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
Py_tss_t var \
= Py_tss_NEEDS_INIT; \
_Pragma("clang diagnostic pop")
# elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
# define PYBIND11_TLS_KEY_INIT(var) \
_Pragma("GCC diagnostic push") /**/ \
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
Py_tss_t var \
= Py_tss_NEEDS_INIT; \
_Pragma("GCC diagnostic pop")
# else
# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT;
# endif
# define PYBIND11_TLS_KEY_CREATE(var) (PyThread_tss_create(&(var)) == 0)
# define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get(&(key))
# define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set(&(key), (value))
# define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set(&(key), nullptr)
# define PYBIND11_TLS_FREE(key) PyThread_tss_delete(&(key))
#define PYBIND11_TLS_KEY_REF Py_tss_t &
#if defined(__clang__)
# define PYBIND11_TLS_KEY_INIT(var) \
_Pragma("clang diagnostic push") /**/ \
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
Py_tss_t var \
= Py_tss_NEEDS_INIT; \
_Pragma("clang diagnostic pop")
#elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
# define PYBIND11_TLS_KEY_INIT(var) \
_Pragma("GCC diagnostic push") /**/ \
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
Py_tss_t var \
= Py_tss_NEEDS_INIT; \
_Pragma("GCC diagnostic pop")
#else
# define PYBIND11_TLS_KEY_REF Py_tss_t *
# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t *var = nullptr;
# define PYBIND11_TLS_KEY_CREATE(var) \
(((var) = PyThread_tss_alloc()) != nullptr && (PyThread_tss_create((var)) == 0))
# define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get((key))
# define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set((key), (value))
# define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set((key), nullptr)
# define PYBIND11_TLS_FREE(key) PyThread_tss_free(key)
# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT;
#endif
#define PYBIND11_TLS_KEY_CREATE(var) (PyThread_tss_create(&(var)) == 0)
#define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get(&(key))
#define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set(&(key), (value))
#define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set(&(key), nullptr)
#define PYBIND11_TLS_FREE(key) PyThread_tss_delete(&(key))

// 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
// even when `A` is the same, non-hidden-visibility type (e.g. from a common include). Under
// libstdc++, this doesn't happen: equality and the type_index hash are based on the type name,
// which works. If not under a known-good stl, provide our own name-based hash and equality
// functions that use the type name.
#if (PYBIND11_INTERNALS_VERSION <= 4 && defined(__GLIBCXX__)) \
|| (PYBIND11_INTERNALS_VERSION >= 5 && !defined(_LIBCPP_VERSION))
#if !defined(_LIBCPP_VERSION)
inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; }
using type_hash = std::hash<std::type_index>;
using type_equal_to = std::equal_to<std::type_index>;
Expand Down Expand Up @@ -193,35 +179,26 @@ struct internals {
std::forward_list<ExceptionTranslator> registered_exception_translators;
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across
// extensions
#if PYBIND11_INTERNALS_VERSION == 4
std::vector<PyObject *> unused_loader_patient_stack_remove_at_v5;
#endif
std::forward_list<std::string> static_strings; // Stores the std::strings backing
// detail::c_str()
std::forward_list<std::string> static_strings; // Stores the std::strings backing
// detail::c_str()
PyTypeObject *static_property_type;
PyTypeObject *default_metaclass;
PyObject *instance_base;
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
PYBIND11_TLS_KEY_INIT(tstate)
#if PYBIND11_INTERNALS_VERSION > 4
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
#endif // PYBIND11_INTERNALS_VERSION > 4
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
PyInterpreterState *istate = nullptr;

#if PYBIND11_INTERNALS_VERSION > 4
// Note that we have to use a std::string to allocate memory to ensure a unique address
// We want unique addresses since we use pointer equality to compare function records
std::string function_record_capsule_name = internals_function_record_capsule_name;
#endif

internals() = default;
internals(const internals &other) = delete;
internals &operator=(const internals &other) = delete;
~internals() {
#if PYBIND11_INTERNALS_VERSION > 4
PYBIND11_TLS_FREE(loader_life_support_tls_key);
#endif // PYBIND11_INTERNALS_VERSION > 4

// This destructor is called *after* Py_Finalize() in finalize_interpreter().
// That *SHOULD BE* fine. The following details what happens when PyThread_tss_free is
Expand Down Expand Up @@ -386,7 +363,7 @@ inline void translate_local_exception(std::exception_ptr p) {

inline object get_python_state_dict() {
object state_dict;
#if PYBIND11_INTERNALS_VERSION <= 4 || defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
#else
# if PY_VERSION_HEX < 0x03090000
Expand Down Expand Up @@ -484,13 +461,12 @@ PYBIND11_NOINLINE internals &get_internals() {
}
PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate);

#if PYBIND11_INTERNALS_VERSION > 4
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) {
pybind11_fail("get_internals: could not successfully initialize the "
"loader_life_support TSS key!");
}
#endif

internals_ptr->istate = tstate->interp;
state_dict[PYBIND11_INTERNALS_ID] = capsule(reinterpret_cast<void *>(internals_pp));
internals_ptr->registered_exception_translators.push_front(&translate_exception);
Expand Down Expand Up @@ -520,40 +496,6 @@ PYBIND11_NOINLINE internals &get_internals() {
struct local_internals {
type_map<type_info *> registered_types_cpp;
std::forward_list<ExceptionTranslator> registered_exception_translators;
#if PYBIND11_INTERNALS_VERSION == 4

// For ABI compatibility, we can't store the loader_life_support TLS key in
// the `internals` struct directly. Instead, we store it in `shared_data` and
// cache a copy in `local_internals`. If we allocated a separate TLS key for
// each instance of `local_internals`, we could end up allocating hundreds of
// TLS keys if hundreds of different pybind11 modules are loaded (which is a
// plausible number).
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)

// Holds the shared TLS key for the loader_life_support stack.
struct shared_loader_life_support_data {
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
shared_loader_life_support_data() {
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) {
pybind11_fail("local_internals: could not successfully initialize the "
"loader_life_support TLS key!");
}
}
// We can't help but leak the TLS key, because Python never unloads extension modules.
};

local_internals() {
auto &internals = get_internals();
// Get or create the `loader_life_support_stack_key`.
auto &ptr = internals.shared_data["_life_support"];
if (!ptr) {
ptr = new shared_loader_life_support_data;
}
loader_life_support_tls_key
= static_cast<shared_loader_life_support_data *>(ptr)->loader_life_support_tls_key;
}
#endif // PYBIND11_INTERNALS_VERSION == 4
};

/// Works like `get_internals`, but for things which are locally registered.
Expand Down Expand Up @@ -660,7 +602,7 @@ const char *c_str(Args &&...args) {

inline const char *get_function_record_capsule_name() {
// On GraalPy, pointer equality of the names is currently not guaranteed
#if PYBIND11_INTERNALS_VERSION > 4 && !defined(GRAALVM_PYTHON)
#if !defined(GRAALVM_PYTHON)
return get_internals().function_record_capsule_name.c_str();
#else
return nullptr;
Expand Down
4 changes: 0 additions & 4 deletions include/pybind11/detail/type_caster_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@ class loader_life_support {

// Store stack pointer in thread-local storage.
static PYBIND11_TLS_KEY_REF get_stack_tls_key() {
#if PYBIND11_INTERNALS_VERSION == 4
return get_local_internals().loader_life_support_tls_key;
#else
return get_internals().loader_life_support_tls_key;
#endif
}
static loader_life_support *get_stack_top() {
return static_cast<loader_life_support *>(PYBIND11_TLS_GET_VALUE(get_stack_tls_key()));
Expand Down
2 changes: 1 addition & 1 deletion pybind11/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ def _to_int(s: str) -> int | str:
return s


__version__ = "2.14.0.dev1"
__version__ = "3.0.0.dev1"
version_info = tuple(_to_int(s) for s in __version__.split("."))
5 changes: 0 additions & 5 deletions tests/test_callbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,7 @@ TEST_SUBMODULE(callbacks, m) {
rec_capsule.set_name(rec_capsule_name);
m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr()));

// This test requires a new ABI version to pass
#if PYBIND11_INTERNALS_VERSION > 4 && !defined(GRAALVM_PYTHON)
// rec_capsule with nullptr name
py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); });
m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr()));
#else
m.add_object("custom_function2", py::none());
#endif
}
4 changes: 1 addition & 3 deletions tests/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,7 @@ def test_custom_func():
assert m.roundtrip(m.custom_function)(4) == 36


@pytest.mark.skipif(
m.custom_function2 is None, reason="Current PYBIND11_INTERNALS_VERSION too low"
)
@pytest.mark.skipif("env.GRAALPY", reason="TODO debug segfault")
def test_custom_func2():
assert m.custom_function2(3) == 27
assert m.roundtrip(m.custom_function2)(3) == 27
Expand Down
1 change: 0 additions & 1 deletion tests/test_unnamed_namespace_a.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ TEST_SUBMODULE(unnamed_namespace_a, m) {
} else {
m.attr("unnamed_namespace_a_any_struct") = py::none();
}
m.attr("PYBIND11_INTERNALS_VERSION") = PYBIND11_INTERNALS_VERSION;
m.attr("defined_WIN32_or__WIN32") =
#if defined(WIN32) || defined(_WIN32)
true;
Expand Down
8 changes: 1 addition & 7 deletions tests/test_unnamed_namespace_a.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@
from pybind11_tests import unnamed_namespace_a as m
from pybind11_tests import unnamed_namespace_b as mb

XFAIL_CONDITION = (
"(m.PYBIND11_INTERNALS_VERSION <= 4 and (m.defined___clang__ or not m.defined___GLIBCXX__))"
" or "
"(m.PYBIND11_INTERNALS_VERSION >= 5 and not m.defined_WIN32_or__WIN32"
" and "
"(m.defined___clang__ or m.defined__LIBCPP_VERSION))"
)
XFAIL_CONDITION = "not m.defined_WIN32_or__WIN32 and (m.defined___clang__ or m.defined__LIBCPP_VERSION)"
XFAIL_REASON = "Known issues: https://github.com/pybind/pybind11/pull/4319"


Expand Down
Loading