
Hello, this is a long and dense email which contains some design ideas which are needed to progress towards supporting custom types in HPy. So far, I have opened github issues to discuss API design questions, but usually I don't get much feedback, so I'm trying to write an email to see whether I have better luck 😅. I think that it's very important to make the right decisions in this phase, so I'd like to hear opinions. The following text is written using markdown syntax and contains snippets of code, so if you prefer you can read with syntax highlighting at this link: https://gist.github.com/antocuni/fd44ee897cc4cf14e874e2e686c60649
To keep compatibility with CPython, we need to generate a trampoline for each method which uses one of HPy calling conventions, e.g.:
HPy_DEF_METH_VARARGS(add_ints)
static HPy add_ints_impl(HPyContext ctx, HPy self, HPy *args, HPy_ssize_t
nargs);
After this, add_ints
is a function of type HPyMeth
(which is different
in
the CPython and the Universal cases), and can be put inside an
HPyMethodDef[]
. Morever, we can put old CPython-style methods by using an
appropriate flag. Note the HPy_METH_*
vs METH_*
flags, and also note
that
if you specify the wrong variant, things will happily explode:
static PyObject *legacy_meth(PyObject *self, PyObject *args);
static HPyMethodDef FooMethods[] = {
...
{"add_ints", add_ints, HPy_METH_VARARGS, ""},
{"legacy_meth", legacy_meth, METH_VARARGS, ""},
...
}
Right now we have a different HPy_DEF_METH_*
macro for the few calling
conventions we support, but to fully support custom types, we need many
more,
for example newfunc
, allocfunc
, getattrfunc
, etc. which are C typedefs
stored in the various tp_new
, tp_alloc
, etc.
Before you ask: we need trampolines for all these functions to:
"Insert" the HPyContext (CPython will call the trampoline without it, but the
_impl
function the user is going to write will need it)Convert
PyObject *
intoHPy
Convert the result
The easiest option is to add more macros and create HPy_DEF_METH_NEWFUNC
&
co., but I wonder whether we can do better.
To start with, I would like to have only ONE macro. This can be done with a preprocessor trick:
#define _HPy_DEF_METH_O(name) ...
#define _HPy_DEF_METH_VARARGS(name) ...
#define HPy_DEF_METH(kind, name) _HPy_DEF_##kind(name)
HPy_DEF_METH(METH_O, foo)
HPy_DEF_METH(METH_VARARGS, bar)
I think this is already an improvement over the existing situation. The quesion is, what is the best syntax to use? Some random proposals:
HPy_DEF_METH(METH_O, ...
: I don't like the repetion of METH; moreover,METH_O
is the *CPython* macro to indicate a legacy function, so you define your meth usingMETH_O
but then you need to useHPy_METH_O
in the method def table.HPy_DEF_METH(HPy_METH_O, ...)
: this is a bit verbose, but it's more explicit.HPy_DEF(HPy_METH_O, ...)
: is it better?HPy_DEFINE(...)
HPy_DEF_METH(O, ...)
HPyMeth_DEF(...)
: I think I like this better than the currentHPy_DEF_METH
.
Keep in mind few facts:
We need to offer also the corresponding
HPy_DECL_METH
, which is needed if you want to put your methods in multiple *.c files. So any syntax we choose, must have a "nice" corresponding syntax for the declaration.We need to use this syntax also for the various
newfunc
,allocfunc
, etc. This is alredy a bit weird becauseMETH_{NOARGS,O,VARARGS}
are flags, whilenewfunc
&co. are typedefs; so, for example:HPyMeth_DEF(METH_O, ...)
andHPyMeth_DEF(newfunc, ...)
?
The following proposal tries to solve/improve these issues, and goes a bit
deeper than just renaming the macros. To start with, we should have a clear
separation between the PyMethodDef[]
(for legacy functions) and
HPyMethodDef[]
, instead of relying on a flag which can be easily
mistaken. For example:
static PyMethodDef LegacyMethods[] = {
{"legacy1", legacy1, METH_NOARGS, ""},
{"legacy2", legacy2, METH_O, ""},
{NULL, NULL, 0, NULL}
};
static HPyMethodDef CoolMethods[] = {
{"foo", foo, METH_NOARGS, ""},
{"bar", bar, METH_O, ""},
{NULL, NULL, 0, NULL}
};
static HPyModuleDef moduledef = {
HPyModuleDef_HEAD_INIT,
...
.m_legacy_methods = LegacyMethods,
.m_methods = CoolMethods,
};
Among the other things, this has advantage of removing the distintion
between
METH_O
and HPy_METH_O
. The original idea of keeping .m_methods = ...
was
to make it as similar as possible to the existing syntax, to make porting
easier without having to modify tons of place. However, my experience with
ultrajson tells me that what you do in practice is to port methods one by
one,
so while you are at it moving the definition from one table to the other is
not a big deal (plus, if we design our types correctly you would get a nice
compilation error if you forget to move the method to the right table).
The main disadvantage of this is that HPyModule_Create
can no longer be an
alias for PyModule_Create
in the CPython ABI mode, but it would need to
be a
custom function which creates the "real" PyModuleDef
. But note that we
need
to implement it anyway for the cpython-universal case, and it's exactly the
same sitation we already have for HPyType_FromSpec
.
Once we decide to use our own HPyModuleDef
, we can become creative, which
brings more API design questions. For example, we could use unions to have a
more type-safe way of specifying methods:
typedef HPy (*HPyMeth_NOARGS)(HPyContext, HPy);
typedef HPy (*HPyMeth_O)(HPyContext, HPy, HPy);
typedef struct {
const char *name;
union {
void *pfunc;
HPyMeth_NOARGS meth_noargs;
HPyMeth_O meth_o;
};
int flags;
} HPyMethodDef;
...
static HPyMethodDef Methods[] = {
{"foo", .meth_noargs = foo, METH_NOARGS},
{"bar", .meth_o = bar, METH_O},
{NULL, NULL, 0}
};
And the same for HPyType_Spec
/HPyType_Slot
:
typedef HPy (*HPy_newfunc)(HPyContext, HPy, HPy, HPy);
typedef HPy (*HPy_allocfunc)(HPyContext, HPy, HPy_ssize_t);
typedef struct {
int slot;
union {
void *p;
HPy_newfunc tp_new;
HPy_allocfunc tp_alloc;
// ...
};
} HPyType_Slot;
static HPyType_Slot slots[] = {
{Py_tp_new, .tp_new = Foo_New},
{Py_tp_alloc, .tp_alloc = Foo_Alloc},
{0, NULL},
};
The biggest advantage is that if you use a function with the wrong signature, you get a nice warning.
The biggest disadvantage is that setting the correct field of the union is obviously not enough: you still need to manually specify the correct flags, but at least it should be "visually evident" if something is wrong (e.g. if you specify
{... .meth_o = foo, METH_NOARGS}
). I don't think there is any way to get a nice compiler error/warning in this case: if you have any idea, please shout :)
If we go down this route, I suggest the following syntax for
HPyMeth_DEFINE
. The nice property is that there is a direct correspondence
between the first argument of the macro and the typedef
of the function
pointer:
typedef HPy (*HPyMeth_NOARGS)(HPyContext, HPy);
typedef HPy (*HPyMeth_O)(HPyContext, HPy, HPy);
typedef HPy (*HPy_newfunc)(HPyContext, HPy, HPy, HPy);
typedef HPy (*HPy_allocfunc)(HPyContext, HPy, HPy_ssize_t);
// ...
HPyMeth_DEFINE(O, foo) // foo_impl is of type HPyMeth_O
HPyMeth_DEFINE(newfunc, Foo_New) // Foo_New_impl is of type HPyMeth_newfunc
Another nice property of this scheme is that we can extend it nicely if in the future we want to go towards an "argument-clinic-like" setup. E.g.:
// L_LDO means "returns a long and takes a long, a double and an object"
typedef long (*HPyMeth_L_LDO)(long, double, HPy);
HPyMeth_DEFINE(L_LDO, foo)
...
But I think that this email is already too long and too dense, so let's not discuss about "argument-clinic-like" now :)
Please let me know what you think!
ciao, Anto

Hi,
On Tue, 19 May 2020 at 12:10, Antonio Cuni <anto.cuni@gmail.com> wrote:
If we go that route, we could go with a syntactically simpler solution:
HPyMeth_DEFINE(O, "foo", foo, "This is the cool foo function") static HPy foo_impl(HPyContext ctx, HPy self, HPy arg) { ... }
static HPyMethodDef CoolMethods[] = { foo, bar, NULL };
static HPyModuleDef moduledef = { HPyModuleDef_HEAD_INIT, ... .m_legacy_methods = LegacyMethods, .m_methods = CoolMethods, };
The name foo
no longer refers to a function of varying signature, but
instead is a small structure that is defined by the HPyMeth_DEFINE
macro. It puts everything we need in one place. Also note that following
your idea to use the same approach for slots, both methods and slots would
be defined by a small structure of the same type---so they could all be put
in the same array in the HPyType_Spec, if we wanted to.
Armin

On Tue, May 19, 2020 at 12:49 PM Armin Rigo <armin.rigo@gmail.com> wrote:
I like the idea; maybe we could even put the "O" after the name, maybe it would be clearer. However, I think that your idea as-is doesn't work: if we define HPyMethodDef foo then we can't use foo in the static initializer: gcc complains that it's not constant. What we can do instead is to tweak CoolMethods to be an array of HPyMethodDef* and use &foo. The following example compiles&works: https://gist.github.com/antocuni/6b52406a05e059ba4c084400ff983a52 I agree it would be nicer to be able to just use "foo", though: can we think of a way to do so?
Another benefit of this solution is that we could get rid of the current hack in which HPyMethodDef.ml_meth is a function which we call at init time to get the two function pointers: we could just store the _impl and the trampoline as separate fields of the HPyMethodDef struct.
Also note that following your idea to use the same approach for slots, both methods and slots would be defined by a small structure of the same type---so they could all be put in the same array in the HPyType_Spec, if we wanted to.
Maybe, but we might be a different macro to define the slot. For example, HPySlot_DEFINE would take something like Py_tp_new instead of the name, and would not allow a docstring. But yes, it could generate a HPyMethodDef struct with all the needed information to distinguish it at runtime; something like this:
HPySlot_DEFINE(Foo_New, tp_new, newfunc) // or maybe "newfunc" could be deduced automatically
ciao, Anto

Hi Anto,
On Tue, 19 May 2020 at 15:09, Antonio Cuni <anto.cuni@gmail.com> wrote:
However, I think that your idea as-is doesn't work: if we define HPyMethodDef foo then we can't use foo in the static initializer: gcc complains that it's not constant. What we can do instead is to tweak CoolMethods to be an array of HPyMethodDef* and use &foo. The following example compiles&works: https://gist.github.com/antocuni/6b52406a05e059ba4c084400ff983a52 I agree it would be nicer to be able to just use "foo", though: can we think of a way to do so?
It is certainly possible, with HPyMeth_DEFINE expanding to:
static HPy foo_impl(HPyContext ctx, HPy self, HPy arg); /* forward */
static PyObject *foo_cpython_wrapper(PyObject *self, PyObject *arg) { ... }
/* typedef _HPyMethod_Desc *PyMethodDef; */
static const _HPyMethod_Desc foo_desc = { METH_O, &foo_impl,
&foo_cpython_wrapper, ... }; static const HPyMethodDef foo = &foo_desc;
Note that hiding tons of things inside macros also comes with drawbacks. For example, if the function foo_impl() is in a different .c file than the HPyType_Desc, then it's not clear what we should make non-static---probably the "foo" constant, but how can the user ask for it?
Another benefit of this solution is that we could get rid of the current hack in which HPyMethodDef.ml_meth is a function which we call at init time to get the two function pointers: we could just store the _impl and the trampoline as separate fields of the HPyMethodDef struct.
Yes, good point. I followed that idea above.
A bientôt, Armin

Re-hi,
On Tue, 19 May 2020 at 15:35, Armin Rigo <armin.rigo@gmail.com> wrote:
Maybe by removing the forward "static foo_impl()" above. It would
make it the user's problem of exporting foo_impl
across multiple .c
files, instead of our macro's problem of exporting foo
. So the user
would write foo_impl
in a static or non-static way as needed, and
then call HPyMeth_DEFINE but at a different place: either just after
foo_impl
(not before), or, in case of multiple .c files, he would
put HPyMeth_DEFINE in the same file as the HPyType_Spec, instead of
along with the foo_impl
functions. If we go this way I'd vote for
removing the automatic name mangling (foo => foo_impl) as well,
otherwise it is hard to grep for where a lone function 'foo_impl' is
actually used, without knowing the trick.
A bientôt, Armin

On Tue, May 19, 2020 at 3:36 PM Armin Rigo <armin.rigo@gmail.com> wrote:
This works only if you define "foo" in the same file, but it breaks as soon as you want to use an external declaration:
typedef struct { long x; long y; } Point;typedef const Point *PointPtr; typedef struct { PointPtr *points; } Polygon; static const Point foo = {1, 2};static const PointPtr pfoo = &foo; /* defined in this file */const PointPtr pbar; /* defined in another file */ static PointPtr points[] = {pfoo, pbar, 0}; /* $ gcc -c static_const.c static_const.c:16:35: error: initializer element is not constant static PointPtr points[] = {pfoo, pbar, 0}; ^~~~*/
(gmail pro-tip: to get syntax highlighting, I wrote that snippet of code in a github issue, then I pressed "preview" and I copied&pasted into gmail :))
Yes, I encountered this problem with ultrajson. For now the quick&dirty solution was to remove "static" from all the macros, but we surely need a better solution. Two ideas:
Use _HPy_HIDDEN for all the HPyMeth_{DEFINE,DECLARE}: this would make symbols available in all the compilation units "by default", and will probably cover 99% of use cases
We don't necessarily need to hide the complexity. We can document the HPyMethodDef structure and its fields, and provide the macros just for convenience: if you have a special need which is not covered by the macro, you can define the HPyMethodDef struct by yourself. The only piece of magic we need to hide for real is the generation of the trampoline; for this we could provide a macro "_HPy_DEFINE_TRAMPOLINE" or so, which people can call if they do things by hand.
The main drawback of this idea is that you separate the implementation of a method and its description: in particular, you can't know the calling convention if you just look at the "impl", and if you change its signature you need to modify two places. I agree that the current CPython solution suffers of basically the same problems, but I think that my idea above is an improvement in this sense. Also, keeping the docstring closer to the actual implementation looks like a big step forward to me.
If we go this way I'd vote for
I agree, but then we have the opposite problem: we need to decide a name to use to hold the HPyMethodDef which is produced by the macro, and this is the name that we will need to use in the HPyModuleDef/HPyType_Spec. Personally, I like the current naming convention because it makes very clear that a python method called "foo" is much more than just a C function: it's a collection of properties, among which there is a C function which implements it. But I am not too attached to this idea, so if we decide that we prefer to do the other way around, it's fine for me.
ciao, Anto

Hi Armin, hi all, I have made some good progress towards implementing the things as discussed here. I have opened a WIP PR: https://github.com/pyhandle/hpy/pull/42
Before going too far and implement it also for the CPython-ABI case, I'd like to receive some feedback and to discuss some of the open questions which are at the end of the PR. Feel free to answer either here or directly on the PR.
ciao, Anto
On Tue, May 19, 2020 at 5:57 PM Antonio Cuni <anto.cuni@gmail.com> wrote:

Salve Anto,
thanks for digging into the issues here. I have a couple of general comments.
The Argument Clinic is part of CPython and I think we can make use of it (or a variant of it) for generating Python level wrappers for C functions. I think code generation is a good thing (*wink*), especially when it comes to boiler-plate code. Basically, the C-API here doesn't have to be nice, nor stable, it just has to do its work efficiently. People shouldn't use it and generally shouldn't write C code for something that is much better expressed at the Python level.
We should focus on the vectorcall calling convention rather than METH_O, VARARGS and friends. Passing arguments as a flat C array is much easier and nicer to handle on both sides than requiring tuples.
CPython's type specs seem ok for defining extension types. I think we can continue to build on that. OTOH, the original PyTypeObject structs together with C99 struct field setting are also quite nice, and give proper C compiler warnings/errors on misassignments, which the "void*" fields in type specs don't. We could also keep the type structs as an abstraction only for *defining* types, not necessarily for the internal type struct layout. Similar to the idea of PyType_Copy() in PEP-3121 [1], which was never implemented [2].
Stefan
[1] https://www.python.org/dev/peps/pep-3121/#specification [2] https://bugs.python.org/issue3760

Hello Stefan,
On Wed, Jun 10, 2020 at 5:44 PM Stefan Behnel <stefan_ml@behnel.de> wrote:
The Argument Clinic is part of CPython and I think we can make use of it (or a variant of it) for generating Python level wrappers for C functions.
yes, this is still among our goals. So far the focus has been on finding a way to "hpy-ify" existing features, but eventually I will want to experiment with solutions to automatically wrap C-level functions and to expose the unwrapped functions to the interpreters, if they want/need it.
Argument Clinic or a variation on the theme is a possible solution. Another possible solution is to use C macros to generate the boilerplate/argument parsing code, similar to what we are doing now for trampolines. You would need a different macro for every different C signature, but these could be autogenerated. The biggest advantage of this solution over Argument Clinic is that the autogen code can be stored in its own .h file instead of being intermixed with user code, but I admit I didn't think too much about this yet.
I don't 100% agree. There are lots of existing extensions written directly in C which needs to be ported manually, and if we want HPy to succeed, we should try to make this task as easy&nice as possible. Numpy is the primary example, of course. But I agree that if we have to choose between being nice or efficient, we should choose the latter.
note that what we call HPyFunc_VARARGS is much more similar to vectorcall than to the old METH_VARARGS. The C signature for it is this (the convention is that HPyFunc_XXX is an enum value to distinguish the signature at runtime, while HPyFunc_xxx is the C typedef):
typedef HPy (*HPyFunc_varargs)(HPyContext ctx, HPy self, HPy *args, HPy_ssize_t nargs);
However, we called it HPyFunc_VARARGS to guide the people to choose it when porting their old METH_VARARGS methods.
Again, the rationale of offering the various _O, _NOARGS, _VARARGS, etc. is that we want to give people a path to hpy-ify their extension which is as smooth as possible. Then, once the extension is hpy-compatible, they can improve by using more advanced features such as HPy-argument-clinic or so. Of course, we should also try to promote usage of HPy-argument-clinic for people writing NEW extensions. But at least for me the focus right now is to make it possible to port the old ones :)
CPython's type specs seem ok for defining extension types. I think we can
Indeed, this is an option. The biggest problem I see with such a solution is that you need to handle the case in which HPyType_Spec grows more fields, especially if you want to load extensions which were compiled with an earlier version. You would need to specify a version number (or the size of the struct) somewhere.
Also, from a purely philosophical point of view, I admit that I like a lot the current solution of using HPyDef, in which slots and methods are listed together: the fact that the "repr" function is a slot instead of a regular method is a CPython implementation detail after all, and other impls could choose to implement it differently. If it weren't for the ambiguity between e.g. Py_nb_add and Py_sq_concat, I would vote for making them regular methods with names such as "__repr__", "__add__", etc., but this is probably off-topic now :).
Speaking of off-topics: do you still plan/are interested in developing an HPy backend for Cython? If not, what do you think is the best/easiest way to develop one?
ciao, Anto

Antonio Cuni schrieb am 11.06.20 um 12:08:
I'm a bit head-under-water with lots of things currently, including Cython 3.0, so HPy isn't a big priority on my side. But, yes, I'm definitely interested. It's going to be some work, though. Even the support for type specs and the limited API isn't finished yet. HPy is yet another "different API" on that front, and it's a heavily moving target. That makes it less interesting to work on right now.
There have been many changes in Cython recently to support C-API differences more easily through feature flags. That's probably what the HPy support would build on, together with lots of macro tweaking here and there. If someone wants to give it a try… ;-)
Stefan

On Thu, Jun 11, 2020 at 12:33 PM Stefan Behnel <stefan_ml@behnel.de> wrote:
True, it might make sense to wait until things have stabilized. My main concern about a Cython HPy backend is how to handle handles (pun intended :)): do you think it will be enough to turn all Py_INCREF/DECREF into HPy_Dup/HPy_Close, or there are cases in which Cython does not emit "paired" sets of incref/decref and which would require fixing?
Speaking of myself personally, my immediate plans are to continue working on HPy itself, the PyPy backend and possibly playing with piconumpy. But maybe someone else in this ML wants to start experiment with it :)
ciao, Anto

Antonio Cuni schrieb am 12.06.20 um 00:42:
There always are. :)
The approach would be to replace the standard calls, and then see what fails and remains to be adapted. Many of these operations are centralised behind helper methods ("make_owned_reference()", "put_incref()", "put_var_xdecref()" and what not), and there is already a "__Pyx_NewRef()" macro that returns a new reference, so getting to 80% might be quite quick, by splitting up different cases into different specialised macro calls, and disabling most low-level optimisation features (as for PyPy and the limited API). The rest remains to be seen then.
Much can probably be handled at the C level in macro code like this:
- Feature flags:
https://github.com/cython/cython/blob/359f89a8c5250d6dea8da025edbc6e39fbd118...
- Compatibility adaptations:
https://github.com/cython/cython/blob/359f89a8c5250d6dea8da025edbc6e39fbd118...
- "__Pyx_NewRef()"
https://github.com/cython/cython/blob/70a58ea21ea240ebf7e59ac4f3fad2b2b8bf37...
Plus the couple of places where direct refcounting calls are generated in Code.py and PyrexTypes.py, or maybe the higher-level calls that lead there.
Stefan

Hi,
On Tue, 19 May 2020 at 12:10, Antonio Cuni <anto.cuni@gmail.com> wrote:
If we go that route, we could go with a syntactically simpler solution:
HPyMeth_DEFINE(O, "foo", foo, "This is the cool foo function") static HPy foo_impl(HPyContext ctx, HPy self, HPy arg) { ... }
static HPyMethodDef CoolMethods[] = { foo, bar, NULL };
static HPyModuleDef moduledef = { HPyModuleDef_HEAD_INIT, ... .m_legacy_methods = LegacyMethods, .m_methods = CoolMethods, };
The name foo
no longer refers to a function of varying signature, but
instead is a small structure that is defined by the HPyMeth_DEFINE
macro. It puts everything we need in one place. Also note that following
your idea to use the same approach for slots, both methods and slots would
be defined by a small structure of the same type---so they could all be put
in the same array in the HPyType_Spec, if we wanted to.
Armin

On Tue, May 19, 2020 at 12:49 PM Armin Rigo <armin.rigo@gmail.com> wrote:
I like the idea; maybe we could even put the "O" after the name, maybe it would be clearer. However, I think that your idea as-is doesn't work: if we define HPyMethodDef foo then we can't use foo in the static initializer: gcc complains that it's not constant. What we can do instead is to tweak CoolMethods to be an array of HPyMethodDef* and use &foo. The following example compiles&works: https://gist.github.com/antocuni/6b52406a05e059ba4c084400ff983a52 I agree it would be nicer to be able to just use "foo", though: can we think of a way to do so?
Another benefit of this solution is that we could get rid of the current hack in which HPyMethodDef.ml_meth is a function which we call at init time to get the two function pointers: we could just store the _impl and the trampoline as separate fields of the HPyMethodDef struct.
Also note that following your idea to use the same approach for slots, both methods and slots would be defined by a small structure of the same type---so they could all be put in the same array in the HPyType_Spec, if we wanted to.
Maybe, but we might be a different macro to define the slot. For example, HPySlot_DEFINE would take something like Py_tp_new instead of the name, and would not allow a docstring. But yes, it could generate a HPyMethodDef struct with all the needed information to distinguish it at runtime; something like this:
HPySlot_DEFINE(Foo_New, tp_new, newfunc) // or maybe "newfunc" could be deduced automatically
ciao, Anto

Hi Anto,
On Tue, 19 May 2020 at 15:09, Antonio Cuni <anto.cuni@gmail.com> wrote:
However, I think that your idea as-is doesn't work: if we define HPyMethodDef foo then we can't use foo in the static initializer: gcc complains that it's not constant. What we can do instead is to tweak CoolMethods to be an array of HPyMethodDef* and use &foo. The following example compiles&works: https://gist.github.com/antocuni/6b52406a05e059ba4c084400ff983a52 I agree it would be nicer to be able to just use "foo", though: can we think of a way to do so?
It is certainly possible, with HPyMeth_DEFINE expanding to:
static HPy foo_impl(HPyContext ctx, HPy self, HPy arg); /* forward */
static PyObject *foo_cpython_wrapper(PyObject *self, PyObject *arg) { ... }
/* typedef _HPyMethod_Desc *PyMethodDef; */
static const _HPyMethod_Desc foo_desc = { METH_O, &foo_impl,
&foo_cpython_wrapper, ... }; static const HPyMethodDef foo = &foo_desc;
Note that hiding tons of things inside macros also comes with drawbacks. For example, if the function foo_impl() is in a different .c file than the HPyType_Desc, then it's not clear what we should make non-static---probably the "foo" constant, but how can the user ask for it?
Another benefit of this solution is that we could get rid of the current hack in which HPyMethodDef.ml_meth is a function which we call at init time to get the two function pointers: we could just store the _impl and the trampoline as separate fields of the HPyMethodDef struct.
Yes, good point. I followed that idea above.
A bientôt, Armin

Re-hi,
On Tue, 19 May 2020 at 15:35, Armin Rigo <armin.rigo@gmail.com> wrote:
Maybe by removing the forward "static foo_impl()" above. It would
make it the user's problem of exporting foo_impl
across multiple .c
files, instead of our macro's problem of exporting foo
. So the user
would write foo_impl
in a static or non-static way as needed, and
then call HPyMeth_DEFINE but at a different place: either just after
foo_impl
(not before), or, in case of multiple .c files, he would
put HPyMeth_DEFINE in the same file as the HPyType_Spec, instead of
along with the foo_impl
functions. If we go this way I'd vote for
removing the automatic name mangling (foo => foo_impl) as well,
otherwise it is hard to grep for where a lone function 'foo_impl' is
actually used, without knowing the trick.
A bientôt, Armin

On Tue, May 19, 2020 at 3:36 PM Armin Rigo <armin.rigo@gmail.com> wrote:
This works only if you define "foo" in the same file, but it breaks as soon as you want to use an external declaration:
typedef struct { long x; long y; } Point;typedef const Point *PointPtr; typedef struct { PointPtr *points; } Polygon; static const Point foo = {1, 2};static const PointPtr pfoo = &foo; /* defined in this file */const PointPtr pbar; /* defined in another file */ static PointPtr points[] = {pfoo, pbar, 0}; /* $ gcc -c static_const.c static_const.c:16:35: error: initializer element is not constant static PointPtr points[] = {pfoo, pbar, 0}; ^~~~*/
(gmail pro-tip: to get syntax highlighting, I wrote that snippet of code in a github issue, then I pressed "preview" and I copied&pasted into gmail :))
Yes, I encountered this problem with ultrajson. For now the quick&dirty solution was to remove "static" from all the macros, but we surely need a better solution. Two ideas:
Use _HPy_HIDDEN for all the HPyMeth_{DEFINE,DECLARE}: this would make symbols available in all the compilation units "by default", and will probably cover 99% of use cases
We don't necessarily need to hide the complexity. We can document the HPyMethodDef structure and its fields, and provide the macros just for convenience: if you have a special need which is not covered by the macro, you can define the HPyMethodDef struct by yourself. The only piece of magic we need to hide for real is the generation of the trampoline; for this we could provide a macro "_HPy_DEFINE_TRAMPOLINE" or so, which people can call if they do things by hand.
The main drawback of this idea is that you separate the implementation of a method and its description: in particular, you can't know the calling convention if you just look at the "impl", and if you change its signature you need to modify two places. I agree that the current CPython solution suffers of basically the same problems, but I think that my idea above is an improvement in this sense. Also, keeping the docstring closer to the actual implementation looks like a big step forward to me.
If we go this way I'd vote for
I agree, but then we have the opposite problem: we need to decide a name to use to hold the HPyMethodDef which is produced by the macro, and this is the name that we will need to use in the HPyModuleDef/HPyType_Spec. Personally, I like the current naming convention because it makes very clear that a python method called "foo" is much more than just a C function: it's a collection of properties, among which there is a C function which implements it. But I am not too attached to this idea, so if we decide that we prefer to do the other way around, it's fine for me.
ciao, Anto

Hi Armin, hi all, I have made some good progress towards implementing the things as discussed here. I have opened a WIP PR: https://github.com/pyhandle/hpy/pull/42
Before going too far and implement it also for the CPython-ABI case, I'd like to receive some feedback and to discuss some of the open questions which are at the end of the PR. Feel free to answer either here or directly on the PR.
ciao, Anto
On Tue, May 19, 2020 at 5:57 PM Antonio Cuni <anto.cuni@gmail.com> wrote:

Salve Anto,
thanks for digging into the issues here. I have a couple of general comments.
The Argument Clinic is part of CPython and I think we can make use of it (or a variant of it) for generating Python level wrappers for C functions. I think code generation is a good thing (*wink*), especially when it comes to boiler-plate code. Basically, the C-API here doesn't have to be nice, nor stable, it just has to do its work efficiently. People shouldn't use it and generally shouldn't write C code for something that is much better expressed at the Python level.
We should focus on the vectorcall calling convention rather than METH_O, VARARGS and friends. Passing arguments as a flat C array is much easier and nicer to handle on both sides than requiring tuples.
CPython's type specs seem ok for defining extension types. I think we can continue to build on that. OTOH, the original PyTypeObject structs together with C99 struct field setting are also quite nice, and give proper C compiler warnings/errors on misassignments, which the "void*" fields in type specs don't. We could also keep the type structs as an abstraction only for *defining* types, not necessarily for the internal type struct layout. Similar to the idea of PyType_Copy() in PEP-3121 [1], which was never implemented [2].
Stefan
[1] https://www.python.org/dev/peps/pep-3121/#specification [2] https://bugs.python.org/issue3760

Hello Stefan,
On Wed, Jun 10, 2020 at 5:44 PM Stefan Behnel <stefan_ml@behnel.de> wrote:
The Argument Clinic is part of CPython and I think we can make use of it (or a variant of it) for generating Python level wrappers for C functions.
yes, this is still among our goals. So far the focus has been on finding a way to "hpy-ify" existing features, but eventually I will want to experiment with solutions to automatically wrap C-level functions and to expose the unwrapped functions to the interpreters, if they want/need it.
Argument Clinic or a variation on the theme is a possible solution. Another possible solution is to use C macros to generate the boilerplate/argument parsing code, similar to what we are doing now for trampolines. You would need a different macro for every different C signature, but these could be autogenerated. The biggest advantage of this solution over Argument Clinic is that the autogen code can be stored in its own .h file instead of being intermixed with user code, but I admit I didn't think too much about this yet.
I don't 100% agree. There are lots of existing extensions written directly in C which needs to be ported manually, and if we want HPy to succeed, we should try to make this task as easy&nice as possible. Numpy is the primary example, of course. But I agree that if we have to choose between being nice or efficient, we should choose the latter.
note that what we call HPyFunc_VARARGS is much more similar to vectorcall than to the old METH_VARARGS. The C signature for it is this (the convention is that HPyFunc_XXX is an enum value to distinguish the signature at runtime, while HPyFunc_xxx is the C typedef):
typedef HPy (*HPyFunc_varargs)(HPyContext ctx, HPy self, HPy *args, HPy_ssize_t nargs);
However, we called it HPyFunc_VARARGS to guide the people to choose it when porting their old METH_VARARGS methods.
Again, the rationale of offering the various _O, _NOARGS, _VARARGS, etc. is that we want to give people a path to hpy-ify their extension which is as smooth as possible. Then, once the extension is hpy-compatible, they can improve by using more advanced features such as HPy-argument-clinic or so. Of course, we should also try to promote usage of HPy-argument-clinic for people writing NEW extensions. But at least for me the focus right now is to make it possible to port the old ones :)
CPython's type specs seem ok for defining extension types. I think we can
Indeed, this is an option. The biggest problem I see with such a solution is that you need to handle the case in which HPyType_Spec grows more fields, especially if you want to load extensions which were compiled with an earlier version. You would need to specify a version number (or the size of the struct) somewhere.
Also, from a purely philosophical point of view, I admit that I like a lot the current solution of using HPyDef, in which slots and methods are listed together: the fact that the "repr" function is a slot instead of a regular method is a CPython implementation detail after all, and other impls could choose to implement it differently. If it weren't for the ambiguity between e.g. Py_nb_add and Py_sq_concat, I would vote for making them regular methods with names such as "__repr__", "__add__", etc., but this is probably off-topic now :).
Speaking of off-topics: do you still plan/are interested in developing an HPy backend for Cython? If not, what do you think is the best/easiest way to develop one?
ciao, Anto

Antonio Cuni schrieb am 11.06.20 um 12:08:
I'm a bit head-under-water with lots of things currently, including Cython 3.0, so HPy isn't a big priority on my side. But, yes, I'm definitely interested. It's going to be some work, though. Even the support for type specs and the limited API isn't finished yet. HPy is yet another "different API" on that front, and it's a heavily moving target. That makes it less interesting to work on right now.
There have been many changes in Cython recently to support C-API differences more easily through feature flags. That's probably what the HPy support would build on, together with lots of macro tweaking here and there. If someone wants to give it a try… ;-)
Stefan

On Thu, Jun 11, 2020 at 12:33 PM Stefan Behnel <stefan_ml@behnel.de> wrote:
True, it might make sense to wait until things have stabilized. My main concern about a Cython HPy backend is how to handle handles (pun intended :)): do you think it will be enough to turn all Py_INCREF/DECREF into HPy_Dup/HPy_Close, or there are cases in which Cython does not emit "paired" sets of incref/decref and which would require fixing?
Speaking of myself personally, my immediate plans are to continue working on HPy itself, the PyPy backend and possibly playing with piconumpy. But maybe someone else in this ML wants to start experiment with it :)
ciao, Anto

Antonio Cuni schrieb am 12.06.20 um 00:42:
There always are. :)
The approach would be to replace the standard calls, and then see what fails and remains to be adapted. Many of these operations are centralised behind helper methods ("make_owned_reference()", "put_incref()", "put_var_xdecref()" and what not), and there is already a "__Pyx_NewRef()" macro that returns a new reference, so getting to 80% might be quite quick, by splitting up different cases into different specialised macro calls, and disabling most low-level optimisation features (as for PyPy and the limited API). The rest remains to be seen then.
Much can probably be handled at the C level in macro code like this:
- Feature flags:
https://github.com/cython/cython/blob/359f89a8c5250d6dea8da025edbc6e39fbd118...
- Compatibility adaptations:
https://github.com/cython/cython/blob/359f89a8c5250d6dea8da025edbc6e39fbd118...
- "__Pyx_NewRef()"
https://github.com/cython/cython/blob/70a58ea21ea240ebf7e59ac4f3fad2b2b8bf37...
Plus the couple of places where direct refcounting calls are generated in Code.py and PyrexTypes.py, or maybe the higher-level calls that lead there.
Stefan
participants (3)
-
Antonio Cuni
-
Armin Rigo
-
Stefan Behnel