Change PYBIND11_MODULE to use multi-phase init (PEP 489) (#5574)

* Change PYBIND11_MODULE to use multi-phase init

Use slots to specify advanced init options (currently just Py_GIL_NOT_USED).
Adds a new function `initialize_multiphase_module_def` to module_, similar to the existing (unchanged) create_extension_module.

* Avoid dynamic allocation and non-trivial destruction

... by using an std::array for the slots.

* Oops, stray cut and paste character

* Remove assignment from placement new, change size fo slots array

* style: pre-commit fixes

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
b-pass
2025-03-21 11:03:34 -04:00
committed by GitHub
parent 97022f83ce
commit 974eba77a5
3 changed files with 104 additions and 17 deletions

View File

@@ -1255,6 +1255,22 @@ private:
bool flag_;
};
PYBIND11_NAMESPACE_BEGIN(detail)
inline bool gil_not_used_option() { return false; }
template <typename F, typename... O>
bool gil_not_used_option(F &&, O &&...o);
template <typename... O>
inline bool gil_not_used_option(mod_gil_not_used f, O &&...o) {
return f.flag() || gil_not_used_option(o...);
}
template <typename F, typename... O>
inline bool gil_not_used_option(F &&, O &&...o) {
return gil_not_used_option(o...);
}
PYBIND11_NAMESPACE_END(detail)
/// Wrapper for Python extension modules
class module_ : public object {
public:
@@ -1362,16 +1378,15 @@ public:
= mod_gil_not_used(false)) {
// module_def is PyModuleDef
// Placement new (not an allocation).
def = new (def)
PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT,
/* m_name */ name,
/* m_doc */ options::show_user_defined_docstrings() ? doc : nullptr,
/* m_size */ -1,
/* m_methods */ nullptr,
/* m_slots */ nullptr,
/* m_traverse */ nullptr,
/* m_clear */ nullptr,
/* m_free */ nullptr};
new (def) PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT,
/* m_name */ name,
/* m_doc */ options::show_user_defined_docstrings() ? doc : nullptr,
/* m_size */ -1,
/* m_methods */ nullptr,
/* m_slots */ nullptr,
/* m_traverse */ nullptr,
/* m_clear */ nullptr,
/* m_free */ nullptr};
auto *m = PyModule_Create(def);
if (m == nullptr) {
if (PyErr_Occurred()) {
@@ -1389,6 +1404,68 @@ public:
// For Python 2, reinterpret_borrow was correct.
return reinterpret_borrow<module_>(m);
}
/// Must be a POD type, and must hold enough entries for all of the possible slots PLUS ONE for
/// the sentinel (0) end slot.
using slots_array = std::array<PyModuleDef_Slot, 3>;
/** \rst
Initialized a module def for use with multi-phase module initialization.
``def`` should point to a statically allocated module_def.
``slots`` must already contain a Py_mod_exec or Py_mod_create slot and will be filled with
additional slots from the supplied options (and the empty sentinel slot).
\endrst */
template <typename... Options>
static object initialize_multiphase_module_def(const char *name,
const char *doc,
module_def *def,
slots_array &slots,
Options &&...options) {
size_t next_slot = 0;
size_t term_slot = slots.size() - 1;
// find the end of the supplied slots
while (next_slot < term_slot && slots[next_slot].slot != 0) {
++next_slot;
}
bool nogil PYBIND11_MAYBE_UNUSED = detail::gil_not_used_option(options...);
if (nogil) {
#if defined(Py_mod_gil) && defined(Py_GIL_DISABLED)
if (next_slot >= term_slot) {
pybind11_fail("initialize_multiphase_module_def: not enough space in slots");
}
slots[next_slot++] = {Py_mod_gil, Py_MOD_GIL_NOT_USED};
#endif
}
// slots must have a zero end sentinel
if (next_slot > term_slot) {
pybind11_fail("initialize_multiphase_module_def: not enough space in slots");
}
slots[next_slot++] = {0, nullptr};
// module_def is PyModuleDef
// Placement new (not an allocation).
new (def) PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT,
/* m_name */ name,
/* m_doc */ options::show_user_defined_docstrings() ? doc : nullptr,
/* m_size */ 0,
/* m_methods */ nullptr,
/* m_slots */ &slots[0],
/* m_traverse */ nullptr,
/* m_clear */ nullptr,
/* m_free */ nullptr};
auto *m = PyModuleDef_Init(def);
if (m == nullptr) {
if (PyErr_Occurred()) {
throw error_already_set();
}
pybind11_fail("Internal error in module_::initialize_multiphase_module_def()");
}
return reinterpret_borrow<object>(m);
}
};
PYBIND11_NAMESPACE_BEGIN(detail)