From 5aac212b12485613735ab75155920a6ba26c42e6 Mon Sep 17 00:00:00 2001 From: stepan Date: Mon, 18 Oct 2021 14:11:04 +0200 Subject: [PATCH 1/3] Make the documentation up to date with the API changes Adds an example module next to the documentation and in the documentation refers to code snippets from that example module. Adds a new make target 'docs-examples-tests' and new CI job that runs this target. --- .github/workflows/ci.yml | 30 +++++++++++ Makefile | 4 ++ docs/api.rst | 96 ++++++++++++++---------------------- docs/simple-example/setup.py | 10 ++++ docs/simple-example/simple.c | 55 +++++++++++++++++++++ docs/simple-example/tests.py | 5 ++ 6 files changed, 142 insertions(+), 58 deletions(-) create mode 100644 docs/simple-example/setup.py create mode 100644 docs/simple-example/simple.c create mode 100644 docs/simple-example/tests.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 942dc4d21..9d5132256 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,6 +138,36 @@ jobs: python -m pip install pytest pytest-valgrind make valgrind + docs_examples_tests: + name: Porting example tests + runs-on: ${{ matrix.os }} + continue-on-error: true + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.9'] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install/Upgrade Python dependencies + run: python -m pip install --upgrade pip wheel + shell: bash + + - name: Install HPy + run: python -m pip install . + + - name: Install pytest + run: | + python -m pip install pytest + - name: Run tests + run: make docs-examples-tests + shell: bash build_docs: name: Build documentation diff --git a/Makefile b/Makefile index 10a3fdf7b..e7dae5ada 100644 --- a/Makefile +++ b/Makefile @@ -44,3 +44,7 @@ infer: valgrind: PYTHONMALLOC=malloc valgrind --suppressions=hpy/tools/valgrind/python.supp --suppressions=hpy/tools/valgrind/hpy.supp --leak-check=full --show-leak-kinds=definite,indirect --log-file=/tmp/valgrind-output python -m pytest --valgrind --valgrind-log=/tmp/valgrind-output test/ + +docs-examples-tests: + python docs/simple-example/setup.py install + pytest docs/simple-example/tests.py diff --git a/docs/api.rst b/docs/api.rst index 6b9e7ba3d..6e7ac896b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -51,17 +51,11 @@ Thus, the following perfectly valid piece of Python/C code:: Py_DECREF(x); // two DECREF on x } -Becomes using HPy API:: +Becomes using HPy API: - void foo(HPyContext *ctx) - { - HPy x = HPyLong_FromLong(ctx, 42); - HPy y = HPy_Dup(ctx, x); - /* ... */ - // we need to close x and y independently - HPy_Close(ctx, x); - HPy_Close(ctx, y); - } +.. literalinclude:: simple-example/simple.c + :start-at: void foo + :end-at: } Calling any HPy function on a closed handle is an error. Calling ``HPy_Close()`` on the same handle twice is an error. Forgetting to call @@ -97,13 +91,11 @@ two different handles which point to the same underlying object, so comparing two handles directly is ill-defined. To prevent this kind of common error (especially when porting existing code to HPy), the ``HPy`` C type is opaque and the C compiler actively forbids comparisons between them. To check for -identity, you can use ``HPy_Is()``:: +identity, you can use ``HPy_Is()``: - void is_same_object(HPyContext *ctx, HPy x, HPy y) - { - // return x == y; // compilation error! - return HPy_Is(ctx, x, y); - } +.. literalinclude:: simple-example/simple.c + :start-at: is_same_object + :end-at: } .. note:: The main benefit of the semantics of handles is that it allows @@ -150,55 +142,48 @@ is assumed that you are already familiar with the existing Python/C API, so we will underline the similarities and the differences with it. We want to create a function named ``myabs`` which takes a single argument and -computes its absolute value:: +computes its absolute value: - #include "hpy.h" - - HPy_DEF_METH_O(myabs) - static HPy myabs_impl(HPyContext *ctx, HPy self, HPy obj) - { - return HPy_Absolute(ctx, obj); - } +.. literalinclude:: simple-example/simple.c + :start-after: // BEGIN: myabs + :end-before: // END: myabs There are a couple of points which are worth noting: - * We use the macro ``HPy_DEF_METH_O`` to declare we are going to define a - HPy function called ``myabs``, which uses the ``METH_O`` calling - convention. As in Python/C, ``METH_O`` means that the function receives a - single argument. + * We use the macro ``HPyDef_METH`` to declare we are going to define a + HPy function called ``myabs``. + + * The function will be available under the name ``"myabs"`` in our Python + module. * The actual C function which implements ``myabs`` is called ``myabs_impl``. - * It receives two arguments of type ``HPy``, which are handles which are - guaranteed to be valid: they are automatically closed by the caller, so - there is no need to call ``HPy_Close`` on them. + * It uses the ``HPyFunc_O`` calling convention. Like ``METH_O`` in CPython, + ``HPyFunc_O`` means that the function receives a single argument on top of + ``self``. + + * ``myabs_impl`` takes two arguments of type ``HPy``, which are handles for ``self`` + and the argument and which are guaranteed to be valid: they are automatically + closed by the caller, so there is no need to call ``HPy_Close`` on them. - * It returns a handle, which has to be closed by the caller. + * ``myabs_impl`` returns a handle, which has to be closed by the caller. * ``HPy_Absolute`` is the equivalent of ``PyNumber_Absolute`` and computes the absolute value of the given argument. -The ``HPy_DEF_METH_O`` macro is needed to maintain compatibility with CPython. +Among other things, +the ``HPy_DEF_METH_O`` macro is needed to maintain compatibility with CPython. In CPython, C functions and methods have a C signature that is different to the one used by HPy: they don't receive an ``HPyContext`` and their arguments have the type ``PyObject *`` instead of ``HPy``. The macro automatically generates a trampoline function whose signature is appropriate for CPython and which calls the ``myabs_impl``. -Now, we can define our module:: +Now, we can define our module: - static HPyMethodDef SimpleMethods[] = { - {"myabs", myabs, HPy_METH_O, "Compute the absolute value of the given argument"}, - {NULL, NULL, 0, NULL} - }; - - static HPyModuleDef moduledef = { - HPyModuleDef_HEAD_INIT, - .m_name = "simple", - .m_doc = "HPy Example", - .m_size = -1, - .m_methods = SimpleMethods - }; +.. literalinclude:: simple-example/simple.c + :start-after: // BEGIN: methodsdef + :end-before: // END: methodsdef This part is very similar to the one you would write in Python/C. Note that we specify ``myabs`` (and **not** ``myabs_impl``) in the method table, and @@ -207,24 +192,19 @@ choice, to minimize the changes needed to port existing extensions, and to make it easier to support hybrid extensions in which some of the methods are still written using the Python/C API. -Finally, ``HPyModuleDef`` is basically the same as the old ``PyModuleDef``. +Finally, ``HPyModuleDef`` is basically the same as the old ``PyModuleDef``: + +.. literalinclude:: simple-example/simple.c + :start-after: // BEGIN: moduledef + :end-before: // END: moduledef Building the module ~~~~~~~~~~~~~~~~~~~~ Let's write a ``setup.py`` to build our extension: -.. code-block:: python - - from setuptools import setup, Extension - - setup( - name="hpy-example", - hpy_ext_modules=[ - Extension('simple', sources=['simple.c']), - ], - setup_requires=['hpy.devel'], - ) +.. literalinclude:: simple-example/setup.py + :language: python We can now build the extension by running ``python setup.py build_ext -i``. On CPython, it will target the :term:`CPython ABI` by default, so you will end up with diff --git a/docs/simple-example/setup.py b/docs/simple-example/setup.py new file mode 100644 index 000000000..f2e851f3c --- /dev/null +++ b/docs/simple-example/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, Extension +from os import path + +setup( + name="hpy-example", + hpy_ext_modules=[ + Extension('simple', sources=[path.join(path.dirname(__file__), 'simple.c')]), + ], + setup_requires=['hpy'], +) diff --git a/docs/simple-example/simple.c b/docs/simple-example/simple.c new file mode 100644 index 000000000..3bdef01f9 --- /dev/null +++ b/docs/simple-example/simple.c @@ -0,0 +1,55 @@ +/* Simple module that defines single simple function "myabs" */ +/* NOTE: snippets from the following code are referenced from the docs! */ + +// For the following two illustrative snippets we just check that they compile: +#include "hpy.h" + +void foo(HPyContext *ctx) +{ + HPy x = HPyLong_FromLong(ctx, 42); + HPy y = HPy_Dup(ctx, x); + /* ... */ + // we need to close x and y independently + HPy_Close(ctx, x); + HPy_Close(ctx, y); +} + +void is_same_object(HPyContext *ctx, HPy x, HPy y) +{ + // return x == y; // compilation error! + return HPy_Is(ctx, x, y); +} + +// ------------- + +// BEGIN: myabs +#include "hpy.h" + +HPyDef_METH(myabs, "myabs", myabs_impl, HPyFunc_O) +static HPy myabs_impl(HPyContext *ctx, HPy self, HPy arg) +{ + return HPy_Absolute(ctx, arg); +} +// END: myabs + +// BEGIN: methodsdef +static HPyDef *SimpleMethods[] = { + &myabs, + NULL, +}; + +static HPyModuleDef simple = { + HPyModuleDef_HEAD_INIT, + .m_name = "simple", + .m_doc = "HPy Example", + .m_size = -1, + .defines = SimpleMethods +}; +// END: methodsdef + +// BEGIN: moduledef +HPy_MODINIT(simple) +HPy init_simple_impl(HPyContext *ctx) { + return HPyModule_Create(ctx, &simple); +} +// END: moduledef \ No newline at end of file diff --git a/docs/simple-example/tests.py b/docs/simple-example/tests.py new file mode 100644 index 000000000..bb4d6d8cd --- /dev/null +++ b/docs/simple-example/tests.py @@ -0,0 +1,5 @@ +import simple + +def test_abs(): + assert simple.myabs(-42) == 42 + assert simple.myabs(42) == 42 \ No newline at end of file From 3eb24df207654378a35c7a9a0de1c98af6274eb4 Mon Sep 17 00:00:00 2001 From: stepan Date: Fri, 22 Oct 2021 16:01:12 +0200 Subject: [PATCH 2/3] Restructure documentation examples * puts all example packages into docs/examples * adds tests for the examples * references snippets from the examples in the docs --- .github/workflows/ci.yml | 2 +- Makefile | 6 ++- docs/api.rst | 47 +++++++++---------- .../{simple.c => mixed-example/mixed.c} | 8 ++-- docs/examples/mixed-example/setup.py | 10 ++++ docs/examples/setup.py | 9 ---- docs/{ => examples}/simple-example/setup.py | 2 +- docs/{ => examples}/simple-example/simple.c | 6 ++- docs/examples/snippets/hpyvarargs.c | 44 +++++++++++++++++ docs/examples/snippets/setup.py | 10 ++++ docs/examples/tests.py | 17 +++++++ docs/simple-example/tests.py | 5 -- 12 files changed, 118 insertions(+), 48 deletions(-) rename docs/examples/{simple.c => mixed-example/mixed.c} (87%) create mode 100644 docs/examples/mixed-example/setup.py delete mode 100644 docs/examples/setup.py rename docs/{ => examples}/simple-example/setup.py (87%) rename docs/{ => examples}/simple-example/simple.c (90%) create mode 100644 docs/examples/snippets/hpyvarargs.c create mode 100644 docs/examples/snippets/setup.py create mode 100644 docs/examples/tests.py delete mode 100644 docs/simple-example/tests.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d5132256..3f2e87bd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,7 +139,7 @@ jobs: make valgrind docs_examples_tests: - name: Porting example tests + name: Documentation examples tests runs-on: ${{ matrix.os }} continue-on-error: true strategy: diff --git a/Makefile b/Makefile index e7dae5ada..e3bcf39f6 100644 --- a/Makefile +++ b/Makefile @@ -46,5 +46,7 @@ valgrind: PYTHONMALLOC=malloc valgrind --suppressions=hpy/tools/valgrind/python.supp --suppressions=hpy/tools/valgrind/hpy.supp --leak-check=full --show-leak-kinds=definite,indirect --log-file=/tmp/valgrind-output python -m pytest --valgrind --valgrind-log=/tmp/valgrind-output test/ docs-examples-tests: - python docs/simple-example/setup.py install - pytest docs/simple-example/tests.py + python docs/examples/simple-example/setup.py install + python docs/examples/mixed-example/setup.py install + python docs/examples/snippets/setup.py install + pytest docs/examples/tests.py diff --git a/docs/api.rst b/docs/api.rst index 6e7ac896b..8edc58074 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -53,9 +53,9 @@ Thus, the following perfectly valid piece of Python/C code:: Becomes using HPy API: -.. literalinclude:: simple-example/simple.c - :start-at: void foo - :end-at: } +.. literalinclude:: examples/simple-example/simple.c + :start-after: // BEGIN: foo + :end-before: // END: foo Calling any HPy function on a closed handle is an error. Calling ``HPy_Close()`` on the same handle twice is an error. Forgetting to call @@ -93,9 +93,9 @@ two handles directly is ill-defined. To prevent this kind of common error and the C compiler actively forbids comparisons between them. To check for identity, you can use ``HPy_Is()``: -.. literalinclude:: simple-example/simple.c - :start-at: is_same_object - :end-at: } +.. literalinclude:: examples/simple-example/simple.c + :start-after: // BEGIN: is_same_object + :end-before: // END: is_same_object .. note:: The main benefit of the semantics of handles is that it allows @@ -144,7 +144,7 @@ will underline the similarities and the differences with it. We want to create a function named ``myabs`` which takes a single argument and computes its absolute value: -.. literalinclude:: simple-example/simple.c +.. literalinclude:: examples/simple-example/simple.c :start-after: // BEGIN: myabs :end-before: // END: myabs @@ -181,7 +181,7 @@ which calls the ``myabs_impl``. Now, we can define our module: -.. literalinclude:: simple-example/simple.c +.. literalinclude:: examples/simple-example/simple.c :start-after: // BEGIN: methodsdef :end-before: // END: methodsdef @@ -194,7 +194,7 @@ still written using the Python/C API. Finally, ``HPyModuleDef`` is basically the same as the old ``PyModuleDef``: -.. literalinclude:: simple-example/simple.c +.. literalinclude:: examples/simple-example/simple.c :start-after: // BEGIN: moduledef :end-before: // END: moduledef @@ -203,7 +203,7 @@ Building the module Let's write a ``setup.py`` to build our extension: -.. literalinclude:: simple-example/setup.py +.. literalinclude:: examples/simple-example/setup.py :language: python We can now build the extension by running ``python setup.py build_ext -i``. On @@ -218,21 +218,20 @@ produce a file called ``simple.hpy.so`` (note that you need to specify python setup.py --hpy-abi=universal build_ext -i +Note: this command will also produce a Python file of the same name, which +loads the HPy module using the `hpy.universal.load` function from the HPy +Python package. + VARARGS calling convention ~~~~~~~~~~~~~~~~~~~~~~~~~~~ If we want to receive more than a single arguments, we need the ``HPy_METH_VARARGS`` calling convention. Let's add a function ``add_ints`` -which adds two integers:: +which adds two integers: - HPy_DEF_METH_VARARGS(add_ints) - static HPy add_ints_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs) - { - long a, b; - if (!HPyArg_Parse(ctx, args, nargs, "ll", &a, &b)) - return HPy_NULL; - return HPyLong_FromLong(ctx, a+b); - } +.. literalinclude:: examples/snippets/hpyvarargs.c + :start-after: // BEGIN: add_ints + :end-before: // END: add_ints There are a few things to note: @@ -254,10 +253,8 @@ There are a few things to note: because ``HPy`` is not a pointer type. Once we have written our function, we can add it to the ``SimpleMethods[]`` -table, which now becomes:: +table, which now becomes: - static HPyMethodDef SimpleMethods[] = { - {"myabs", myabs, HPy_METH_O, "Compute the absolute value of the given argument"}, - {"add_ints", add_ints, HPy_METH_VARARGS, "Add two integers"}, - {NULL, NULL, 0, NULL} - }; +.. literalinclude:: examples/snippets/hpyvarargs.c + :start-after: // BEGIN: methodsdef + :end-before: // END: methodsdef diff --git a/docs/examples/simple.c b/docs/examples/mixed-example/mixed.c similarity index 87% rename from docs/examples/simple.c rename to docs/examples/mixed-example/mixed.c index 49349a595..8d86db806 100644 --- a/docs/examples/simple.c +++ b/docs/examples/mixed-example/mixed.c @@ -35,16 +35,16 @@ static PyMethodDef py_defines[] = { static HPyModuleDef moduledef = { HPyModuleDef_HEAD_INIT, - .m_name = "simple", - .m_doc = "HPy Example", + .m_name = "mixed", + .m_doc = "HPy Example of mixing CPython API and HPy API", .m_size = -1, .defines = hpy_defines, .legacy_methods = py_defines }; -HPy_MODINIT(simple) -static HPy init_simple_impl(HPyContext *ctx) +HPy_MODINIT(mixed) +static HPy init_mixed_impl(HPyContext *ctx) { HPy m; m = HPyModule_Create(ctx, &moduledef); diff --git a/docs/examples/mixed-example/setup.py b/docs/examples/mixed-example/setup.py new file mode 100644 index 000000000..75af73458 --- /dev/null +++ b/docs/examples/mixed-example/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, Extension +from os import path + +setup( + name="hpy-mixed-example", + hpy_ext_modules=[ + Extension('mixed', sources=[path.join(path.dirname(__file__), 'mixed.c')]), + ], + setup_requires=['hpy'], +) diff --git a/docs/examples/setup.py b/docs/examples/setup.py deleted file mode 100644 index ab6c20caf..000000000 --- a/docs/examples/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -from setuptools import setup, Extension - -setup( - name="hpy-example", - hpy_ext_modules=[ - Extension('simple', sources=['simple.c']), - ], - setup_requires=['hpy.devel'], -) diff --git a/docs/simple-example/setup.py b/docs/examples/simple-example/setup.py similarity index 87% rename from docs/simple-example/setup.py rename to docs/examples/simple-example/setup.py index f2e851f3c..af8186194 100644 --- a/docs/simple-example/setup.py +++ b/docs/examples/simple-example/setup.py @@ -2,7 +2,7 @@ from os import path setup( - name="hpy-example", + name="hpy-simple-example", hpy_ext_modules=[ Extension('simple', sources=[path.join(path.dirname(__file__), 'simple.c')]), ], diff --git a/docs/simple-example/simple.c b/docs/examples/simple-example/simple.c similarity index 90% rename from docs/simple-example/simple.c rename to docs/examples/simple-example/simple.c index 3bdef01f9..68624c733 100644 --- a/docs/simple-example/simple.c +++ b/docs/examples/simple-example/simple.c @@ -4,6 +4,7 @@ // For the following two illustrative snippets we just check that they compile: #include "hpy.h" +// BEGIN: foo void foo(HPyContext *ctx) { HPy x = HPyLong_FromLong(ctx, 42); @@ -13,12 +14,15 @@ void foo(HPyContext *ctx) HPy_Close(ctx, x); HPy_Close(ctx, y); } +// END: foo -void is_same_object(HPyContext *ctx, HPy x, HPy y) +// BEGIN: is_same_object +int is_same_object(HPyContext *ctx, HPy x, HPy y) { // return x == y; // compilation error! return HPy_Is(ctx, x, y); } +// END: is_same_object // ------------- diff --git a/docs/examples/snippets/hpyvarargs.c b/docs/examples/snippets/hpyvarargs.c new file mode 100644 index 000000000..4269a0110 --- /dev/null +++ b/docs/examples/snippets/hpyvarargs.c @@ -0,0 +1,44 @@ +/* Simple module that defines simple functions "myabs" and "add_ints" with + * varargs calling convention */ +/* NOTE: snippets from the following code are referenced from the docs! */ + +#include "hpy.h" + +// This is here to make the module look like an incremental change to simple-example +HPyDef_METH(myabs, "myabs", myabs_impl, HPyFunc_O) +static HPy myabs_impl(HPyContext *ctx, HPy self, HPy arg) +{ + return HPy_Absolute(ctx, arg); +} + +// BEGIN: add_ints +HPyDef_METH(add_ints, "add_ints", add_ints_impl, HPyFunc_VARARGS) +static HPy add_ints_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs) +{ + long a, b; + if (!HPyArg_Parse(ctx, NULL, args, nargs, "ll", &a, &b)) + return HPy_NULL; + return HPyLong_FromLong(ctx, a+b); +} +// END: add_ints + +// BEGIN: methodsdef +static HPyDef *SimpleMethods[] = { + &myabs, + &add_ints, + NULL, +}; +// END: methodsdef + +static HPyModuleDef simple = { + HPyModuleDef_HEAD_INIT, + .m_name = "hpyvarargs", + .m_doc = "HPy Example of varargs calling convention", + .m_size = -1, + .defines = SimpleMethods +}; + +HPy_MODINIT(hpyvarargs) +HPy init_hpyvarargs_impl(HPyContext *ctx) { + return HPyModule_Create(ctx, &simple); +} diff --git a/docs/examples/snippets/setup.py b/docs/examples/snippets/setup.py new file mode 100644 index 000000000..ecae755fe --- /dev/null +++ b/docs/examples/snippets/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, Extension +from os import path + +setup( + name="hpy-snippets", + hpy_ext_modules=[ + Extension('hpyvarargs', sources=[path.join(path.dirname(__file__), 'hpyvarargs.c')]), + ], + setup_requires=['hpy'], +) diff --git a/docs/examples/tests.py b/docs/examples/tests.py new file mode 100644 index 000000000..c24a0b565 --- /dev/null +++ b/docs/examples/tests.py @@ -0,0 +1,17 @@ +import simple +import mixed +import hpyvarargs + + +def test_simple_abs(): + assert simple.myabs(-42) == 42 + assert simple.myabs(42) == 42 + + +def test_hpyvarargs(): + assert hpyvarargs.add_ints(40, 2) == 42 + + +def test_mixed_add_ints(): + assert mixed.add_ints_legacy(40, 2) == 42 + assert mixed.add_ints(40, 2) == 42 \ No newline at end of file diff --git a/docs/simple-example/tests.py b/docs/simple-example/tests.py deleted file mode 100644 index bb4d6d8cd..000000000 --- a/docs/simple-example/tests.py +++ /dev/null @@ -1,5 +0,0 @@ -import simple - -def test_abs(): - assert simple.myabs(-42) == 42 - assert simple.myabs(42) == 42 \ No newline at end of file From bf22920da0e154a413bfe9c6fcca6d849a147e33 Mon Sep 17 00:00:00 2001 From: stepan Date: Mon, 25 Oct 2021 22:15:40 +0200 Subject: [PATCH 3/3] More restructuring and improvements of the docs --- docs/api.rst | 55 ++++++++++++++---------- docs/examples/mixed-example/mixed.c | 5 +++ docs/examples/simple-example/simple.c | 36 ++++------------ docs/examples/snippets/hpyvarargs.c | 12 ++++-- docs/examples/snippets/setup.py | 1 + docs/examples/snippets/snippets.c | 60 +++++++++++++++++++++++++++ docs/examples/tests.py | 9 +++- 7 files changed, 124 insertions(+), 54 deletions(-) create mode 100644 docs/examples/snippets/snippets.c diff --git a/docs/api.rst b/docs/api.rst index 8edc58074..8ca4f7381 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -53,7 +53,7 @@ Thus, the following perfectly valid piece of Python/C code:: Becomes using HPy API: -.. literalinclude:: examples/simple-example/simple.c +.. literalinclude:: examples/snippets/snippets.c :start-after: // BEGIN: foo :end-before: // END: foo @@ -81,7 +81,7 @@ references to the same object results in the very same ``PyObject *`` pointer. Thus, it is possible to compare C pointers by equality to check whether they point to the same object:: - void is_same_object(PyObject *x, PyObject *y) + int is_same_object(PyObject *x, PyObject *y) { return x == y; } @@ -93,7 +93,7 @@ two handles directly is ill-defined. To prevent this kind of common error and the C compiler actively forbids comparisons between them. To check for identity, you can use ``HPy_Is()``: -.. literalinclude:: examples/simple-example/simple.c +.. literalinclude:: examples/snippets/snippets.c :start-after: // BEGIN: is_same_object :end-before: // END: is_same_object @@ -126,7 +126,7 @@ Python/C function call, where there is no notion of context. One of the reasons to include ``HPyContext`` from the day one is to be future-proof: it is conceivable to use it to hold the interpreter or the thread state in the future, in particular when there will be support for -sub-interpreter. Another possible usage could be to embed different versions +sub-interpreters. Another possible usage could be to embed different versions or implementations of Python inside the same process. Moreover, ``HPyContext`` is used by the :term:`HPy Universal ABI` to contain a @@ -158,12 +158,12 @@ There are a couple of points which are worth noting: * The actual C function which implements ``myabs`` is called ``myabs_impl``. - * It uses the ``HPyFunc_O`` calling convention. Like ``METH_O`` in CPython, + * It uses the ``HPyFunc_O`` calling convention. Like ``METH_O`` in Python/C API, ``HPyFunc_O`` means that the function receives a single argument on top of ``self``. - * ``myabs_impl`` takes two arguments of type ``HPy``, which are handles for ``self`` - and the argument and which are guaranteed to be valid: they are automatically + * ``myabs_impl`` takes two arguments of type ``HPy``: handles for ``self`` + and the argument, which are guaranteed to be valid. They are automatically closed by the caller, so there is no need to call ``HPy_Close`` on them. * ``myabs_impl`` returns a handle, which has to be closed by the caller. @@ -171,13 +171,20 @@ There are a couple of points which are worth noting: * ``HPy_Absolute`` is the equivalent of ``PyNumber_Absolute`` and computes the absolute value of the given argument. -Among other things, -the ``HPy_DEF_METH_O`` macro is needed to maintain compatibility with CPython. -In CPython, C functions and methods have a C signature that is different to -the one used by HPy: they don't receive an ``HPyContext`` and their arguments -have the type ``PyObject *`` instead of ``HPy``. The macro automatically -generates a trampoline function whose signature is appropriate for CPython and -which calls the ``myabs_impl``. + * We also do not call ``HPy_Close`` on the result returned to the caller. + We must return a valid handle. + +.. note:: + Among other things, + the ``HPyDef_METH`` macro is needed to maintain compatibility with CPython. + In CPython, C functions and methods have a C signature that is different to + the one used by HPy: they don't receive an ``HPyContext`` and their arguments + have the type ``PyObject *`` instead of ``HPy``. The macro automatically + generates a trampoline function whose signature is appropriate for CPython and + which calls the ``myabs_impl``. This trampoline is then used from both the + CPython ABI and the CPython implementation of the universal ABI, but other + implementations of the universal ABI will usually call directly the HPy + function itself. Now, we can define our module: @@ -186,11 +193,14 @@ Now, we can define our module: :end-before: // END: methodsdef This part is very similar to the one you would write in Python/C. Note that -we specify ``myabs`` (and **not** ``myabs_impl``) in the method table, and -that we have to indicate the calling convention again. This is a deliberate -choice, to minimize the changes needed to port existing extensions, and to -make it easier to support hybrid extensions in which some of the methods are -still written using the Python/C API. +we specify ``myabs`` (and **not** ``myabs_impl``) in the method table. There +is also the ``.legacy_methods`` field, which allows to add methods that use the +Python/C API, i.e., the value should be an array of ``PyMethodDef``. This +feature enables support for hybrid extensions in which some of the methods +are still written using the Python/C 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``: @@ -218,9 +228,10 @@ produce a file called ``simple.hpy.so`` (note that you need to specify python setup.py --hpy-abi=universal build_ext -i -Note: this command will also produce a Python file of the same name, which -loads the HPy module using the `hpy.universal.load` function from the HPy -Python package. +.. note:: + This command will also produce a Python file named ``simple.py``, which + loads the HPy module using the ``universal.load`` function from + the ``hpy`` Python package. VARARGS calling convention ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/examples/mixed-example/mixed.c b/docs/examples/mixed-example/mixed.c index 8d86db806..ddee4324f 100644 --- a/docs/examples/mixed-example/mixed.c +++ b/docs/examples/mixed-example/mixed.c @@ -1,3 +1,8 @@ +/* Simple C module that shows how to mix CPython API and HPY. + * At the moment, this code is not referenced from the documentation, but it is + * tested nonetheless. + */ + #include "hpy.h" /* a HPy style function */ diff --git a/docs/examples/simple-example/simple.c b/docs/examples/simple-example/simple.c index 68624c733..37d6f461c 100644 --- a/docs/examples/simple-example/simple.c +++ b/docs/examples/simple-example/simple.c @@ -1,30 +1,9 @@ -/* Simple module that defines single simple function "myabs" */ -/* NOTE: snippets from the following code are referenced from the docs! */ - -// For the following two illustrative snippets we just check that they compile: -#include "hpy.h" - -// BEGIN: foo -void foo(HPyContext *ctx) -{ - HPy x = HPyLong_FromLong(ctx, 42); - HPy y = HPy_Dup(ctx, x); - /* ... */ - // we need to close x and y independently - HPy_Close(ctx, x); - HPy_Close(ctx, y); -} -// END: foo - -// BEGIN: is_same_object -int is_same_object(HPyContext *ctx, HPy x, HPy y) -{ - // return x == y; // compilation error! - return HPy_Is(ctx, x, y); -} -// END: is_same_object - -// ------------- +/* Simple C module that defines single simple function "myabs". + * We need to have a separate standalone package for those snippets, because we + * want to show the source code in its entirety, including the HPyDef array + * initialization, the module definition, and the setup.py script, so there is + * no room left for mixing these code snippets with other code snippets. + */ // BEGIN: myabs #include "hpy.h" @@ -47,7 +26,8 @@ static HPyModuleDef simple = { .m_name = "simple", .m_doc = "HPy Example", .m_size = -1, - .defines = SimpleMethods + .defines = SimpleMethods, + .legacy_methods = NULL }; // END: methodsdef diff --git a/docs/examples/snippets/hpyvarargs.c b/docs/examples/snippets/hpyvarargs.c index 4269a0110..6a8c4c27a 100644 --- a/docs/examples/snippets/hpyvarargs.c +++ b/docs/examples/snippets/hpyvarargs.c @@ -1,6 +1,12 @@ -/* Simple module that defines simple functions "myabs" and "add_ints" with - * varargs calling convention */ -/* NOTE: snippets from the following code are referenced from the docs! */ +/* Simple C module that defines simple functions "myabs" and "add_ints". + * + * This module represents an incremental change over the "simple" package + * and shows how to add a method with VARARGS calling convention. + * + * We need to have a separate standalone C module for those snippets, because we + * want to show the source code including the HPyDef array initialization, so + * there is no room left for adding other entry points for other code snippets. + */ #include "hpy.h" diff --git a/docs/examples/snippets/setup.py b/docs/examples/snippets/setup.py index ecae755fe..12b870557 100644 --- a/docs/examples/snippets/setup.py +++ b/docs/examples/snippets/setup.py @@ -5,6 +5,7 @@ name="hpy-snippets", hpy_ext_modules=[ Extension('hpyvarargs', sources=[path.join(path.dirname(__file__), 'hpyvarargs.c')]), + Extension('snippets', sources=[path.join(path.dirname(__file__), 'snippets.c')]), ], setup_requires=['hpy'], ) diff --git a/docs/examples/snippets/snippets.c b/docs/examples/snippets/snippets.c new file mode 100644 index 000000000..f339acc7f --- /dev/null +++ b/docs/examples/snippets/snippets.c @@ -0,0 +1,60 @@ +/* Module with various code snippets used in the docs. + * All code snippets should be put into this file if possible. Notable + * exception are code snippets showing definition of the module or the + * HPyDef array initialization. Remember to also add tests to ../tests.py + */ +#include "hpy.h" + +// ------------------------------------ +// Snippets used in api.rst + +// BEGIN: foo +void foo(HPyContext *ctx) +{ + HPy x = HPyLong_FromLong(ctx, 42); + HPy y = HPy_Dup(ctx, x); + /* ... */ + // we need to close x and y independently + HPy_Close(ctx, x); + HPy_Close(ctx, y); +} +// END: foo + +// BEGIN: is_same_object +int is_same_object(HPyContext *ctx, HPy x, HPy y) +{ + // return x == y; // compilation error! + return HPy_Is(ctx, x, y); +} +// END: is_same_object + +// dummy entry point so that we can test the snippets: +HPyDef_METH(test_foo_and_is_same_object, "test_foo_and_is_same_object", + test_foo_and_is_same_object_impl, HPyFunc_VARARGS) +static HPy test_foo_and_is_same_object_impl(HPyContext *ctx, HPy self, + HPy *args, HPy_ssize_t nargs) +{ + foo(ctx); // not much we can test here + return HPyLong_FromLong(ctx, is_same_object(ctx, args[0], args[1])); +} + +// ------------------------------------ +// Dummy module definition, so that we can test the snippets + +static HPyDef *Methods[] = { + &test_foo_and_is_same_object, + NULL, +}; + +static HPyModuleDef snippets = { + HPyModuleDef_HEAD_INIT, + .m_name = "snippets", + .m_doc = "Various HPy code snippets for the docs", + .m_size = -1, + .defines = Methods +}; + +HPy_MODINIT(snippets) +HPy init_snippets_impl(HPyContext *ctx) { + return HPyModule_Create(ctx, &snippets); +} \ No newline at end of file diff --git a/docs/examples/tests.py b/docs/examples/tests.py index c24a0b565..04baa2718 100644 --- a/docs/examples/tests.py +++ b/docs/examples/tests.py @@ -1,6 +1,7 @@ import simple import mixed import hpyvarargs +import snippets def test_simple_abs(): @@ -14,4 +15,10 @@ def test_hpyvarargs(): def test_mixed_add_ints(): assert mixed.add_ints_legacy(40, 2) == 42 - assert mixed.add_ints(40, 2) == 42 \ No newline at end of file + assert mixed.add_ints(40, 2) == 42 + + +def test_snippets(): + x = 2 + assert snippets.test_foo_and_is_same_object(x, x) == 1 + assert snippets.test_foo_and_is_same_object(x, 42) == 0