Skip to content

Commit

Permalink
Merge pull request #390 from fangerer/fa/vectorcall
Browse files Browse the repository at this point in the history
Introduce HPy call protocol
  • Loading branch information
fangerer committed Mar 31, 2023
2 parents 8c2d7ed + 00fef37 commit 3da37d6
Show file tree
Hide file tree
Showing 76 changed files with 1,884 additions and 471 deletions.
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 self, const HPy *args, size_t nargs,
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, self);
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 self, const HPy *args, size_t nargs,
HPy kwnames)
{
HPy tmp = call_impl(ctx, self, 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 self, 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 self, 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 self, 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, self, 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

0 comments on commit 3da37d6

Please sign in to comment.