Skip to content

Commit 974eba7

Browse files
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>
1 parent 97022f8 commit 974eba7

File tree

3 files changed

+104
-17
lines changed

3 files changed

+104
-17
lines changed

include/pybind11/detail/common.h

+16-7
Original file line numberDiff line numberDiff line change
@@ -372,11 +372,9 @@
372372
#define PYBIND11_CATCH_INIT_EXCEPTIONS \
373373
catch (pybind11::error_already_set & e) { \
374374
pybind11::raise_from(e, PyExc_ImportError, "initialization failed"); \
375-
return nullptr; \
376375
} \
377376
catch (const std::exception &e) { \
378377
::pybind11::set_error(PyExc_ImportError, e.what()); \
379-
return nullptr; \
380378
}
381379

382380
/** \rst
@@ -404,6 +402,7 @@
404402
return pybind11_init(); \
405403
} \
406404
PYBIND11_CATCH_INIT_EXCEPTIONS \
405+
return nullptr; \
407406
} \
408407
PyObject *pybind11_init()
409408

@@ -447,23 +446,33 @@
447446
PYBIND11_WARNING_PUSH
448447
PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
449448
#define PYBIND11_MODULE(name, variable, ...) \
450-
static ::pybind11::module_::module_def PYBIND11_CONCAT(pybind11_module_def_, name) \
451-
PYBIND11_MAYBE_UNUSED; \
452-
PYBIND11_MAYBE_UNUSED \
449+
static ::pybind11::module_::module_def PYBIND11_CONCAT(pybind11_module_def_, name); \
450+
static ::pybind11::module_::slots_array PYBIND11_CONCAT(pybind11_module_slots_, name); \
451+
static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \
453452
static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \
454453
PYBIND11_PLUGIN_IMPL(name) { \
455454
PYBIND11_CHECK_PYTHON_VERSION \
456455
PYBIND11_ENSURE_INTERNALS_READY \
457-
auto m = ::pybind11::module_::create_extension_module( \
456+
auto &slots = PYBIND11_CONCAT(pybind11_module_slots_, name); \
457+
slots[0] \
458+
= {Py_mod_exec, reinterpret_cast<void *>(&PYBIND11_CONCAT(pybind11_exec_, name))}; \
459+
slots[1] = {0, nullptr}; \
460+
auto m = ::pybind11::module_::initialize_multiphase_module_def( \
458461
PYBIND11_TOSTRING(name), \
459462
nullptr, \
460463
&PYBIND11_CONCAT(pybind11_module_def_, name), \
464+
slots, \
461465
##__VA_ARGS__); \
466+
return m.ptr(); \
467+
} \
468+
int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject * pm) { \
462469
try { \
470+
auto m = pybind11::reinterpret_borrow<::pybind11::module_>(pm); \
463471
PYBIND11_CONCAT(pybind11_init_, name)(m); \
464-
return m.ptr(); \
472+
return 0; \
465473
} \
466474
PYBIND11_CATCH_INIT_EXCEPTIONS \
475+
return -1; \
467476
} \
468477
void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ & (variable))
469478
PYBIND11_WARNING_POP

include/pybind11/embed.h

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
return m.ptr(); \
5050
} \
5151
PYBIND11_CATCH_INIT_EXCEPTIONS \
52+
return nullptr; \
5253
} \
5354
PYBIND11_EMBEDDED_MODULE_IMPL(name) \
5455
::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name)( \

include/pybind11/pybind11.h

+87-10
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,22 @@ class mod_gil_not_used {
12551255
bool flag_;
12561256
};
12571257

1258+
PYBIND11_NAMESPACE_BEGIN(detail)
1259+
1260+
inline bool gil_not_used_option() { return false; }
1261+
template <typename F, typename... O>
1262+
bool gil_not_used_option(F &&, O &&...o);
1263+
template <typename... O>
1264+
inline bool gil_not_used_option(mod_gil_not_used f, O &&...o) {
1265+
return f.flag() || gil_not_used_option(o...);
1266+
}
1267+
template <typename F, typename... O>
1268+
inline bool gil_not_used_option(F &&, O &&...o) {
1269+
return gil_not_used_option(o...);
1270+
}
1271+
1272+
PYBIND11_NAMESPACE_END(detail)
1273+
12581274
/// Wrapper for Python extension modules
12591275
class module_ : public object {
12601276
public:
@@ -1362,16 +1378,15 @@ class module_ : public object {
13621378
= mod_gil_not_used(false)) {
13631379
// module_def is PyModuleDef
13641380
// Placement new (not an allocation).
1365-
def = new (def)
1366-
PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT,
1367-
/* m_name */ name,
1368-
/* m_doc */ options::show_user_defined_docstrings() ? doc : nullptr,
1369-
/* m_size */ -1,
1370-
/* m_methods */ nullptr,
1371-
/* m_slots */ nullptr,
1372-
/* m_traverse */ nullptr,
1373-
/* m_clear */ nullptr,
1374-
/* m_free */ nullptr};
1381+
new (def) PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT,
1382+
/* m_name */ name,
1383+
/* m_doc */ options::show_user_defined_docstrings() ? doc : nullptr,
1384+
/* m_size */ -1,
1385+
/* m_methods */ nullptr,
1386+
/* m_slots */ nullptr,
1387+
/* m_traverse */ nullptr,
1388+
/* m_clear */ nullptr,
1389+
/* m_free */ nullptr};
13751390
auto *m = PyModule_Create(def);
13761391
if (m == nullptr) {
13771392
if (PyErr_Occurred()) {
@@ -1389,6 +1404,68 @@ class module_ : public object {
13891404
// For Python 2, reinterpret_borrow was correct.
13901405
return reinterpret_borrow<module_>(m);
13911406
}
1407+
1408+
/// Must be a POD type, and must hold enough entries for all of the possible slots PLUS ONE for
1409+
/// the sentinel (0) end slot.
1410+
using slots_array = std::array<PyModuleDef_Slot, 3>;
1411+
1412+
/** \rst
1413+
Initialized a module def for use with multi-phase module initialization.
1414+
1415+
``def`` should point to a statically allocated module_def.
1416+
``slots`` must already contain a Py_mod_exec or Py_mod_create slot and will be filled with
1417+
additional slots from the supplied options (and the empty sentinel slot).
1418+
\endrst */
1419+
template <typename... Options>
1420+
static object initialize_multiphase_module_def(const char *name,
1421+
const char *doc,
1422+
module_def *def,
1423+
slots_array &slots,
1424+
Options &&...options) {
1425+
size_t next_slot = 0;
1426+
size_t term_slot = slots.size() - 1;
1427+
1428+
// find the end of the supplied slots
1429+
while (next_slot < term_slot && slots[next_slot].slot != 0) {
1430+
++next_slot;
1431+
}
1432+
1433+
bool nogil PYBIND11_MAYBE_UNUSED = detail::gil_not_used_option(options...);
1434+
if (nogil) {
1435+
#if defined(Py_mod_gil) && defined(Py_GIL_DISABLED)
1436+
if (next_slot >= term_slot) {
1437+
pybind11_fail("initialize_multiphase_module_def: not enough space in slots");
1438+
}
1439+
slots[next_slot++] = {Py_mod_gil, Py_MOD_GIL_NOT_USED};
1440+
#endif
1441+
}
1442+
1443+
// slots must have a zero end sentinel
1444+
if (next_slot > term_slot) {
1445+
pybind11_fail("initialize_multiphase_module_def: not enough space in slots");
1446+
}
1447+
slots[next_slot++] = {0, nullptr};
1448+
1449+
// module_def is PyModuleDef
1450+
// Placement new (not an allocation).
1451+
new (def) PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT,
1452+
/* m_name */ name,
1453+
/* m_doc */ options::show_user_defined_docstrings() ? doc : nullptr,
1454+
/* m_size */ 0,
1455+
/* m_methods */ nullptr,
1456+
/* m_slots */ &slots[0],
1457+
/* m_traverse */ nullptr,
1458+
/* m_clear */ nullptr,
1459+
/* m_free */ nullptr};
1460+
auto *m = PyModuleDef_Init(def);
1461+
if (m == nullptr) {
1462+
if (PyErr_Occurred()) {
1463+
throw error_already_set();
1464+
}
1465+
pybind11_fail("Internal error in module_::initialize_multiphase_module_def()");
1466+
}
1467+
return reinterpret_borrow<object>(m);
1468+
}
13921469
};
13931470

13941471
PYBIND11_NAMESPACE_BEGIN(detail)

0 commit comments

Comments
 (0)