Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support vectorcall protocol #390

Merged
merged 42 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
38dda51
Support type slot tp_call
fangerer Apr 12, 2022
c730af6
Add initial support for the vectorcall protocol
fangerer Dec 1, 2022
53ecb09
Add test for vectorcall
fangerer Dec 1, 2022
06b4dc6
Support vectorcall protocol on Python 3.8
fangerer Dec 2, 2022
cf94dbc
Remove helper macro HPyDef_VECTORCALL_OFFSET
fangerer Dec 14, 2022
2a8cedf
Rename and move HPyType_VECTORCALL
fangerer Dec 14, 2022
386de46
Introduce slot HPy_tp_vectorcall_default
fangerer Dec 13, 2022
249228a
Set default vectorcall function in HPy_New
fangerer Dec 14, 2022
ac11985
Wrap inherited tp_new to set default vectorcall function
fangerer Dec 14, 2022
39d1839
Automatically set tp_call if not provided
fangerer Dec 14, 2022
04d2171
Add/update vectorcall tests
fangerer Dec 14, 2022
25838ff
Introduce HPyVectorcall_Set
fangerer Dec 14, 2022
6f61d3c
Add tests for HPyVectorcall_Set
fangerer Dec 14, 2022
9715ac0
Add test: no default vectorcall function for var objects
fangerer Dec 15, 2022
86c604d
Add sanity check for basicsize
fangerer Dec 15, 2022
03bf117
Add test for using legacy way of implementing vectorcall protocol
fangerer Dec 15, 2022
7d0300b
Various minor cleanups
fangerer Dec 15, 2022
6b091cd
Satisfy infer analyzer
fangerer Dec 16, 2022
4226d81
Ensure aligned (hidden) vectorcall field
fangerer Dec 16, 2022
af5aa8f
Implement HPyFunc_KEYWORDS with METH_FASTCALL|METH_KEYWORDS
fangerer Mar 15, 2023
08592be
Migrate HPyArg_ParseKeywords to new keywords calling convention
fangerer Mar 16, 2023
b9a3baf
Update tests/docs using HPyArg_ParseKeywords
fangerer Mar 15, 2023
57312d6
Migrate tests using HPy_tp_call
fangerer Mar 16, 2023
f32a418
Add test for HPyArg_ParseKeywordsDict
fangerer Mar 16, 2023
6fdf5e6
Reject auto-generating conversion of unknown types
fangerer Mar 16, 2023
ee51517
Introduce sig HPyFunc_NEWFUNC and use it for slot HPy_tp_new
fangerer Mar 16, 2023
57792a1
Update proof-of-concept to new keywords calling convention
fangerer Mar 16, 2023
b86b349
Align signatures of HPyFunc_newfunc and HPyFunc_initproc
fangerer Mar 17, 2023
5f05bab
Implement HPy calls with vectorcall (make it the default)
fangerer Mar 17, 2023
f78ea09
Remove HPy_VECTORCALL_ARGUMENTS_OFFSET
fangerer Mar 17, 2023
ee39252
Remove MAX_ALIGN_T; directly use _HPy_MaxAlign_t
fangerer Mar 17, 2023
d7896b9
Rename _HPy_set_vectorcall_default to _HPy_vectorcall_init
fangerer Mar 17, 2023
88b1c44
Handle interaction between HPy_tp_call and member '__vectorcalloffset__'
fangerer Mar 17, 2023
e535b80
Fix: set flag _Py_TPFLAGS_HAVE_VECTORCALL if member '__vectorcalloffs…
fangerer Mar 20, 2023
4989ee6
Reject using HPy call protocol for legacy types with basicsize==0
fangerer Mar 20, 2023
6b37f46
Rename HPyVectorcall_Set to HPy_SetCallFunction
fangerer Mar 20, 2023
3b5f727
Provide helper to convert from HPy calling convention to legacy
fangerer Mar 21, 2023
78bf1cd
Add section about HPy call protocol to porting guide
fangerer Mar 21, 2023
ecc13aa
Include added API in docs
fangerer Mar 21, 2023
a69ff3c
Add doc for HPyDef_CALL_FUNCTION
fangerer Mar 21, 2023
a0458fd
Align signature of HPyFunc_VARARGS to HPyFunc_KEYWORDS
fangerer Mar 22, 2023
00fef37
Rename 'callable' to 'self' in docs example
fangerer Mar 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-reference/function-index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ HPy Core API Function Index
* :c:func:`HPy_Rshift`
* :c:func:`HPy_SetAttr`
* :c:func:`HPy_SetAttr_s`
* :c:func:`HPy_SetCallFunction`
* :c:func:`HPy_SetItem`
* :c:func:`HPy_SetItem_i`
* :c:func:`HPy_SetItem_s`
Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference/hpy-object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ HPy Object
==========

.. autocmodule:: autogen/public_api.h
:members: HPy_IsTrue,HPy_GetAttr,HPy_GetAttr_s,HPy_HasAttr,HPy_HasAttr_s,HPy_SetAttr,HPy_SetAttr_s,HPy_GetItem,HPy_GetItem_s,HPy_GetItem_i,HPy_SetItem,HPy_SetItem_s,HPy_SetItem_i,HPy_DelItem,HPy_DelItem_s,HPy_DelItem_i,HPy_Type,HPy_TypeCheck,HPy_Is,HPy_Repr,HPy_Str,HPy_ASCII,HPy_Bytes,HPy_RichCompare,HPy_RichCompareBool,HPy_Hash
:members: HPy_IsTrue,HPy_GetAttr,HPy_GetAttr_s,HPy_HasAttr,HPy_HasAttr_s,HPy_SetAttr,HPy_SetAttr_s,HPy_GetItem,HPy_GetItem_s,HPy_GetItem_i,HPy_SetItem,HPy_SetItem_s,HPy_SetItem_i,HPy_DelItem,HPy_DelItem_s,HPy_DelItem_i,HPy_Type,HPy_TypeCheck,HPy_Is,HPy_Repr,HPy_Str,HPy_ASCII,HPy_Bytes,HPy_RichCompare,HPy_RichCompareBool,HPy_Hash,HPy_SetCallFunction
2 changes: 1 addition & 1 deletion docs/api-reference/hpy-type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ Defining slots, methods, members, and get-set descriptors for types and modules
is done with HPy definition (represented by C struct :c:struct:`HPyDef`).

.. autocmodule:: hpy/hpydef.h
:members: HPyDef,HPyDef_Kind,HPySlot,HPyMeth,HPyMember_FieldType,HPyMember,HPyGetSet,HPyDef_SLOT,HPyDef_METH,HPyDef_MEMBER,HPyDef_GET,HPyDef_SET,HPyDef_GETSET
:members: HPyDef,HPyDef_Kind,HPySlot,HPyMeth,HPyMember_FieldType,HPyMember,HPyGetSet,HPyDef_SLOT,HPyDef_METH,HPyDef_MEMBER,HPyDef_GET,HPyDef_SET,HPyDef_GETSET,HPyDef_CALL_FUNCTION
2 changes: 1 addition & 1 deletion docs/examples/hpytype-example/simple_type.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ static int Point_z_set(HPyContext *ctx, HPy self, HPy value, void *closure)

// BEGIN: slots
HPyDef_SLOT(Point_new, HPy_tp_new)
static HPy Point_new_impl(HPyContext *ctx, HPy cls, HPy *args,
static HPy Point_new_impl(HPyContext *ctx, HPy cls, const HPy *args,
HPy_ssize_t nargs, HPy kw)
{
long x, y;
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/mixed-example/mixed.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

/* a HPy style function */
HPyDef_METH(add_ints, "add_ints", HPyFunc_VARARGS)
static HPy add_ints_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs)
static HPy add_ints_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs)
{
long a, b;
if (!HPyArg_Parse(ctx, NULL, args, nargs, "ll", &a, &b))
Expand Down
196 changes: 196 additions & 0 deletions docs/examples/snippets/hpycall.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#include <hpy.h>

// BEGIN EuclideanVectorObject
typedef struct {
long x;
long y;
} EuclideanVectorObject;
HPyType_HELPERS(EuclideanVectorObject)
// END EuclideanVectorObject

// BEGIN HPy_tp_call
HPyDef_SLOT(call, HPy_tp_call)
static HPy
call_impl(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs,
fangerer marked this conversation as resolved.
Show resolved Hide resolved
HPy kwnames)
{
static const char *keywords[] = { "x1", "y1", NULL };
long x1, y1;
HPyTracker ht;
if (!HPyArg_ParseKeywords(ctx, &ht, args, nargs, kwnames, "ll", keywords,
&x1, &y1)) {
return HPy_NULL;
}
EuclideanVectorObject *data = EuclideanVectorObject_AsStruct(ctx, callable);
return HPyLong_FromLong(ctx, data->x * x1 + data->y * y1);
}
// END HPy_tp_call

// BEGIN HPy_SetCallFunction
HPyDef_CALL_FUNCTION(special_call)
static HPy
special_call_impl(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs,
HPy kwnames)
{
HPy tmp = call_impl(ctx, callable, args, nargs, kwnames);
HPy res = HPy_Negative(ctx, tmp);
HPy_Close(ctx, tmp);
return res;
}

HPyDef_SLOT(new, HPy_tp_new)
static HPy
new_impl(HPyContext *ctx, HPy cls, const HPy *args, HPy_ssize_t nargs, HPy kw)
{
static const char *keywords[] = { "x", "y", "use_special_call", NULL };
HPyTracker ht;
long x, y;
HPy use_special_call = ctx->h_False;
if (!HPyArg_ParseKeywordsDict(ctx, &ht, args, nargs, kw, "ll|O", keywords,
&x, &y, &use_special_call)) {
return HPy_NULL;
}
EuclideanVectorObject *vector;
HPy h_point = HPy_New(ctx, cls, &vector);
if (HPy_IsNull(h_point)) {
HPyTracker_Close(ctx, ht);
return HPy_NULL;
}
if (HPy_IsTrue(ctx, use_special_call) &&
HPy_SetCallFunction(ctx, h_point, &special_call) < 0) {
HPyTracker_Close(ctx, ht);
HPy_Close(ctx, h_point);
return HPy_NULL;
}
HPyTracker_Close(ctx, ht);
vector->x = x;
vector->y = y;
return h_point;
}
// END HPy_SetCallFunction

// BEGIN FooObject
typedef struct {
void *a;
HPyCallFunction call_func;
void *b;
} FooObject;
HPyType_HELPERS(FooObject)
// END FooObject

// BEGIN vectorcalloffset
HPyDef_MEMBER(Foo_call_func_offset, "__vectorcalloffset__", HPyMember_HPYSSIZET,
offsetof(FooObject, call_func), .readonly=1)

HPyDef_CALL_FUNCTION(Foo_call_func)
static HPy
Foo_call_func_impl(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs,
HPy kwnames)
{
return HPyUnicode_FromString(ctx,
"hello manually initialized call function");
}

HPyDef_SLOT(Foo_new, HPy_tp_new)
static HPy Foo_new_impl(HPyContext *ctx, HPy cls, const HPy *args,
HPy_ssize_t nargs, HPy kw)
{
FooObject *data;
HPy h_obj = HPy_New(ctx, cls, &data);
if (HPy_IsNull(h_obj))
return HPy_NULL;
data->call_func = Foo_call_func;
return h_obj;
}
// END vectorcalloffset

// BEGIN pack_args
// function using legacy 'tp_call' calling convention
static HPy
Pack_call_legacy(HPyContext *ctx, HPy callable, HPy args, HPy kwd)
{
// use 'args' and 'kwd'
return HPy_Dup(ctx, ctx->h_None);
}

// function using HPy calling convention
HPyDef_SLOT(Pack_call, HPy_tp_call)
static HPy
Pack_call_impl(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs,
HPy kwnames)
{
HPy args_tuple, kwd;
HPy result;
if (!HPyHelpers_PackArgsAndKeywords(ctx, args, nargs, kwnames,
&args_tuple, &kwd)) {
return HPy_NULL;
}
result = Pack_call_legacy(ctx, callable, args_tuple, kwd);
HPy_Close(ctx, args_tuple);
HPy_Close(ctx, kwd);
return result;
}
// END pack_args

static HPyDef *Point_defines[] = {
&call,
&new,
NULL
};
static HPyType_Spec EuclideanVector_spec = {
.name = "hpycall.EuclideanVector",
.basicsize = sizeof(EuclideanVectorObject),
.builtin_shape = SHAPE(EuclideanVectorObject),
.defines = Point_defines
};

static HPyDef *Foo_defines[] = {
&Foo_call_func_offset,
&Foo_new,
NULL
};
static HPyType_Spec Foo_spec = {
.name = "hpycall.Foo",
.basicsize = sizeof(FooObject),
.builtin_shape = SHAPE(FooObject),
.defines = Foo_defines
};

static HPyDef *Pack_defines[] = {
&Pack_call,
NULL
};
static HPyType_Spec Pack_spec = {
.name = "hpycall.Pack",
.defines = Pack_defines
};

HPyDef_SLOT(init, HPy_mod_exec)
static int init_impl(HPyContext *ctx, HPy m)
{
if (!HPyHelpers_AddType(ctx, m, "EuclideanVector", &EuclideanVector_spec, NULL)) {
return -1;
}
if (!HPyHelpers_AddType(ctx, m, "Foo", &Foo_spec, NULL)) {
return -1;
}
if (!HPyHelpers_AddType(ctx, m, "Pack", &Pack_spec, NULL)) {
return -1;
}
return 0;
}

static HPyDef *moduledefs[] = {
&init,
NULL
};

static HPyModuleDef moduledef = {
.doc = "HPy call protocol usage example",
.size = 0,
.legacy_methods = NULL,
.defines = moduledefs,

};

HPy_MODINIT(hpycall, moduledef)
2 changes: 1 addition & 1 deletion docs/examples/snippets/hpyvarargs.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ static HPy myabs_impl(HPyContext *ctx, HPy self, HPy arg)

// BEGIN: add_ints
HPyDef_METH(add_ints, "add_ints", HPyFunc_VARARGS)
static HPy add_ints_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs)
static HPy add_ints_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs)
{
long a, b;
if (!HPyArg_Parse(ctx, NULL, args, nargs, "ll", &a, &b))
Expand Down
1 change: 1 addition & 0 deletions docs/examples/snippets/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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')]),
Extension('hpycall', sources=[path.join(path.dirname(__file__), 'hpycall.c')]),
],
ext_modules=[
Extension('legacyinit', sources=[path.join(path.dirname(__file__), 'legacyinit.c')]),
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/snippets/snippets.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ int is_same_object(HPyContext *ctx, HPy x, HPy y)
// dummy entry point so that we can test the snippets:
HPyDef_METH(test_foo_and_is_same_object, "test_foo_and_is_same_object", HPyFunc_VARARGS)
static HPy test_foo_and_is_same_object_impl(HPyContext *ctx, HPy self,
HPy *args, HPy_ssize_t nargs)
const HPy *args, size_t nargs)
{
foo(ctx); // not much we can test here
return HPyLong_FromLong(ctx, is_same_object(ctx, args[0], args[1]));
Expand All @@ -52,7 +52,7 @@ static HPy test_leak_stacktrace_impl(HPyContext *ctx, HPy self)

// BEGIN: add
HPyDef_METH(add, "add", HPyFunc_VARARGS)
static HPy add_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs)
static HPy add_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs)
{
if (nargs != 2) {
HPyErr_SetString(ctx, ctx->h_TypeError, "expected exactly two args");
Expand Down
11 changes: 11 additions & 0 deletions docs/examples/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import snippets
import simple_type
import builtin_type
import hpycall


def test_simple_abs():
Expand Down Expand Up @@ -111,3 +112,13 @@ def test_trace_mode_output():
# Rudimentary check that the output contains what we have in the documentation
out = result.stdout.decode('latin-1')
assert 'get_call_counts()["ctx_Add"] == 1' in out

def test_call_dot_product():
vec = hpycall.EuclideanVector(4, 5)
assert vec(6, 7) == 4 * 6 + 5 * 7
vec = hpycall.EuclideanVector(4, 5, use_special_call=True)
assert vec(6, 7) == -(4 * 6 + 5 * 7)
foo = hpycall.Foo()
assert foo() == 'hello manually initialized call function'
pack = hpycall.Pack()
assert pack() is None
2 changes: 1 addition & 1 deletion docs/porting-example/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ that implements the dot product between two points:

The changes are similar to those used in porting the ``norm`` method, except:

- We use ``HPyArg_Parse`` instead of ``HPyArg_ParseKeywords``.
- We use ``HPyArg_Parse`` instead of ``HPyArg_ParseKeywordsDict``.

- We opted not to use an ``HPyTracker`` by passing ``NULL`` as the pointer to the
tracker when calling ``HPyArg_Parse``. There is no reason not to use a
Expand Down
10 changes: 6 additions & 4 deletions docs/porting-example/steps/step_02_hpy_legacy.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,22 @@ int Point_traverse_impl(void *self, HPyFunc_visitproc visit, void *arg)

// this is a method for creating a Point
HPyDef_SLOT(Point_init, HPy_tp_init)
int Point_init_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs, HPy kw)
int Point_init_impl(HPyContext *ctx, HPy self, const HPy *args,
HPy_ssize_t nargs, HPy kw)
{
static const char *kwlist[] = {"x", "y", "obj", NULL};
PointObject *p = PointObject_AsStruct(ctx, self);
p->x = 0.0;
p->y = 0.0;
HPy obj = HPy_NULL;
HPyTracker ht;
if (!HPyArg_ParseKeywords(ctx, &ht, args, nargs, kw, "|ddO", kwlist,
&p->x, &p->y, &obj))
if (!HPyArg_ParseKeywordsDict(ctx, &ht, args, nargs, kw, "|ddO", kwlist,
&p->x, &p->y, &obj))
return -1;
if (HPy_IsNull(obj))
obj = ctx->h_None;
// INCREF not needed because HPyArg_ParseKeywords does not steal a reference
/* INCREF not needed because HPyArg_ParseKeywordsDict does not steal a
reference */
HPyField_Store(ctx, self, &p->obj, obj);
HPyTracker_Close(ctx, ht);
return 0;
Expand Down
10 changes: 6 additions & 4 deletions docs/porting-example/steps/step_03_hpy_final.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,22 @@ int Point_traverse_impl(void *self, HPyFunc_visitproc visit, void *arg)

// this is a method for creating a Point
HPyDef_SLOT(Point_init, HPy_tp_init)
int Point_init_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs, HPy kw)
int Point_init_impl(HPyContext *ctx, HPy self, const HPy *args,
HPy_ssize_t nargs, HPy kw)
{
static const char *kwlist[] = {"x", "y", "obj", NULL};
PointObject *p = PointObject_AsStruct(ctx, self);
p->x = 0.0;
p->y = 0.0;
HPy obj = HPy_NULL;
HPyTracker ht;
if (!HPyArg_ParseKeywords(ctx, &ht, args, nargs, kw, "|ddO", kwlist,
if (!HPyArg_ParseKeywordsDict(ctx, &ht, args, nargs, kw, "|ddO", kwlist,
&p->x, &p->y, &obj))
return -1;
if (HPy_IsNull(obj))
obj = ctx->h_None;
// INCREF not needed because HPyArg_ParseKeywords does not steal a reference
/* INCREF not needed because HPyArg_ParseKeywordsDict does not steal a
reference */
HPyField_Store(ctx, self, &p->obj, obj);
HPyTracker_Close(ctx, ht);
return 0;
Expand All @@ -82,7 +84,7 @@ HPy Point_norm_impl(HPyContext *ctx, HPy self)

// this is an HPy function that uses Point
HPyDef_METH(dot, "dot", HPyFunc_VARARGS, .doc="Dot product.")
HPy dot_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs)
HPy dot_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs)
{
HPy point1, point2;
if (!HPyArg_Parse(ctx, NULL, args, nargs, "OO", &point1, &point2))
Expand Down
Loading