Skip to content

Commit

Permalink
Update the documentation to multi-phase module init
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-s committed Jan 31, 2023
1 parent c1464c1 commit 4d9ca6b
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 20 deletions.
17 changes: 13 additions & 4 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,19 @@ is also the ``.legacy_methods`` field, which allows to add methods that use the
feature enables support for hybrid extensions in which some of the methods
are still written using the ``Python.h`` API.

.. This would be perhaps good place to add a link to the porting tutorial
once it's merged
Finally, ``HPyModuleDef`` is basically the same as the old ``PyModuleDef``:
Note that the HPy module does not specify its name. HPy does not support the legacy
single phase module initialization and the only module initialization approach is
the multi-phase initialization (PEP 451). With multi-phase module initialization,
the name of the module is always taken from the ``ModuleSpec``, i.e., most likely
from the name used in the ``import {{name}}`` statement that imported your module.

This is the only difference stemming from multi-phase module initialization in this
simple example.
As long as there is no need for any further initialization, we can just "register"
our module using the ``HPy_MODINIT`` convenience macro. The first argument is the
name of the extension file and is needed for HPy, among other things, to be able
to generate the entry point for CPython called ``PyInit_{{name}}``. The second argument
is the ``HPyModuleDef`` we just defined.

.. literalinclude:: examples/simple-example/simple.c
:start-after: // BEGIN: moduledef
Expand Down
22 changes: 22 additions & 0 deletions docs/examples/snippets/hpyinit.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "hpy.h"

// BEGIN
HPyDef_SLOT(my_exec, HPy_mod_exec)
int my_exec_impl(HPyContext *ctx, HPy mod) {

// Some initialization: add types, constants, ...

return 0; // success
}

static HPyDef *Methods[] = {
&my_exec, // HPyDef_SLOT macro generated `my_exec` for us
NULL,
};

static HPyModuleDef mod_def = {
.defines = Methods
};

HPy_MODINIT(hpyinit, mod_def)
// END
20 changes: 20 additions & 0 deletions docs/examples/snippets/legacyinit.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <Python.h>

// BEGIN
static struct PyModuleDef mod_def = {
PyModuleDef_HEAD_INIT,
.m_name = "legacyinit",
.m_size = -1
};

PyMODINIT_FUNC
PyInit_legacyinit(void)
{
PyObject *mod = PyModule_Create(&mod_def);
if (mod == NULL) return NULL;

// Some initialization: add types, constants, ...

return mod;
}
// END
4 changes: 4 additions & 0 deletions docs/examples/snippets/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
hpy_ext_modules=[
Extension('hpyvarargs', sources=[path.join(path.dirname(__file__), 'hpyvarargs.c')]),
Extension('snippets', sources=[path.join(path.dirname(__file__), 'snippets.c')]),
Extension('hpyinit', sources=[path.join(path.dirname(__file__), 'hpyinit.c')]),
],
ext_modules=[
Extension('legacyinit', sources=[path.join(path.dirname(__file__), 'legacyinit.c')]),
],
setup_requires=['hpy'],
)
21 changes: 21 additions & 0 deletions docs/porting-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,24 @@ Buffer slots for HPy types are specified using slots ``HPy_bf_getbuffer`` and
matching PyType_Spec slots, ``Py_bf_getbuffer`` and ``Py_bf_releasebuffer``, are
only available starting from CPython 3.9.

Multi-phase Module Initialization
---------------------------------

HPy supports only multi-phase module initialization (PEP 451). This means that
the module object is typically created by interpreter from the ``HPyModuleDef``
specification and there is no "init" function. However, the module can define
one or more ``HPy_mod_exec`` slots, which will be executed just after the module
object is created. Inside the code of those slots, one can usually perform the same
initialization as before.

Example of legacy single phase module initialization that uses Python/C API:

.. literalinclude:: examples/snippets/legacyinit.c
:start-after: // BEGIN
:end-before: // END

The same code structure ported to HPy and multi-phase module initialization:

.. literalinclude:: examples/snippets/hpyinit.c
:start-after: // BEGIN
:end-before: // END
38 changes: 22 additions & 16 deletions test/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,8 @@ class DefaultExtensionTemplate(object):

INIT_TEMPLATE = textwrap.dedent(
"""
HPyDef_SLOT(generated_init, HPy_mod_exec)
static int generated_init_impl(HPyContext *ctx, HPy m)
{
// Shouldn't really happen, but jut to silence the unused label warning
if (HPy_IsNull(m))
goto MODINIT_ERROR;
%(init_types)s
return 0;
MODINIT_ERROR:
return -1;
}
static HPyDef *moduledefs[] = {
%(defines)s
&generated_init,
NULL
};
%(globals_defs)s
Expand All @@ -107,6 +92,23 @@ class DefaultExtensionTemplate(object):
HPy_MODINIT(%(name)s, moduledef)
""")

INIT_TEMPLATE_WITH_TYPES_INIT = textwrap.dedent(
"""
HPyDef_SLOT(generated_init, HPy_mod_exec)
static int generated_init_impl(HPyContext *ctx, HPy m)
{
// Shouldn't really happen, but jut to silence the unused label warning
if (HPy_IsNull(m))
goto MODINIT_ERROR;
%(init_types)s
return 0;
MODINIT_ERROR:
return -1;
}
""" + INIT_TEMPLATE)

r_marker = re.compile(r"^\s*@([A-Za-z_]+)(\(.*\))?$")

def __init__(self, src, name):
Expand Down Expand Up @@ -164,7 +166,11 @@ def INIT(self):
};''') % NL_INDENT.join(self.globals_table)
globals_field = '.globals = module_globals'

exp = self.INIT_TEMPLATE % {
template = self.INIT_TEMPLATE
if init_types:
template = self.INIT_TEMPLATE_WITH_TYPES_INIT
self.EXPORT('generated_init')
exp = template % {
'legacy_methods': self.legacy_methods,
'defines': NL_INDENT.join(self.defines_table),
'init_types': init_types,
Expand Down

0 comments on commit 4d9ca6b

Please sign in to comment.