Skip to content

Commit

Permalink
Fixes for Python 3.7 compatiblity
Browse files Browse the repository at this point in the history
  • Loading branch information
fangerer committed Dec 15, 2022
1 parent 746382d commit 2f6531c
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 53 deletions.
185 changes: 141 additions & 44 deletions hpy/devel/src/runtime/ctx_type.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ static inline void* _HPy_Payload(PyObject *obj, const HPyType_BuiltinShape shape
static bool has_tp_traverse(HPyType_Spec *hpyspec);
static bool needs_hpytype_dealloc(HPyType_Spec *hpyspec);

// only for Python 3.7 compatibility
#if !HAVE_VECTORCALL_SUPPORT
#define _Py_TPFLAGS_HAVE_VECTORCALL 0
#endif

/* This is a hack: we need some extra space to store random data on the
type objects created by HPyType_FromSpec(). We allocate a structure
Expand All @@ -131,7 +135,7 @@ typedef struct {
uint16_t magic;
HPyFunc_traverseproc tp_traverse_impl;
HPyFunc_destroyfunc tp_destroy_impl;
vectorcallfunc tp_vectorcall_default_trampoline;
cpy_vectorcallfunc tp_vectorcall_default_trampoline;
HPyType_BuiltinShape shape;
char name[];
} HPyType_Extra_t;
Expand All @@ -155,25 +159,25 @@ static inline HPyType_BuiltinShape _HPyType_Get_Shape(PyTypeObject *tp) {
return _is_HPyType(tp) ? _HPyType_EXTRA(tp)->shape : HPyType_BuiltinShape_Legacy;
}

#if HAVE_VECTORCALL_SUPPORT
static inline vectorcallfunc _HPyType_get_vectorcall_default(PyTypeObject *tp) {
static inline cpy_vectorcallfunc _HPyType_get_vectorcall_default(PyTypeObject *tp) {
return _is_HPyType(tp) ?
_HPyType_EXTRA(tp)->tp_vectorcall_default_trampoline : NULL;
}

static inline void _HPy_set_vectorcall_func(PyTypeObject *tp, PyObject *o, vectorcallfunc f) {
#if HAVE_VECTORCALL_SUPPORT
static inline void _HPy_set_vectorcall_func(PyTypeObject *tp, PyObject *o, cpy_vectorcallfunc f) {
const Py_ssize_t offset = tp->tp_vectorcall_offset;
assert(offset > 0);
memcpy((char *) o + offset, &f, sizeof(f));
}

static inline void _HPy_set_vectorcall_default(PyTypeObject *tp, PyObject *o) {
if (PyType_HasFeature(tp, _Py_TPFLAGS_HAVE_VECTORCALL)) {
vectorcallfunc vectorcall_default = _HPyType_get_vectorcall_default(tp);
cpy_vectorcallfunc vectorcall_default = _HPyType_get_vectorcall_default(tp);
_HPy_set_vectorcall_func(tp, o, vectorcall_default);
}
}
#endif
#endif /* HAVE_VECTORCALL_SUPPORT */

static HPyType_Extra_t *_HPyType_Extra_Alloc(const char *name, HPyType_BuiltinShape shape)
{
Expand Down Expand Up @@ -270,7 +274,111 @@ hpyobject_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
_HPy_set_vectorcall_default(type, result);
return result;
}
#endif
#else

int
_HPyStack_UnpackDict(PyObject *args, Py_ssize_t nargs, PyObject *kwargs,
PyObject *const **p_stack, PyObject **p_kwnames)
{
PyObject **stack, **kwstack;
Py_ssize_t nkwargs = 0;
Py_ssize_t pos, i;
PyObject *key, *value;
PyObject *kwnames;

assert(nargs >= 0);
assert(kwargs == NULL || PyDict_CheckExact(kwargs));

if (kwargs == NULL || (nkwargs = PyDict_GET_SIZE(kwargs)) == 0) {
stack = PyMem_Malloc((nargs + nkwargs) * sizeof(stack[0]));
if (stack == NULL) {
PyErr_NoMemory();
return -1;
}
/* Copy positional arguments */
for (i = 0; i < nargs; i++) {
stack[i] = PyTuple_GET_ITEM(args, i);
Py_INCREF(stack[i]);
}
*p_stack = stack;
*p_kwnames = NULL;
return 0;
}

if ((size_t)nargs > PY_SSIZE_T_MAX / sizeof(stack[0]) - (size_t)nkwargs) {
PyErr_NoMemory();
return -1;
}

stack = PyMem_Malloc((nargs + nkwargs) * sizeof(stack[0]));
if (stack == NULL) {
PyErr_NoMemory();
return -1;
}

kwnames = PyTuple_New(nkwargs);
if (kwnames == NULL) {
PyMem_Free(stack);
return -1;
}

/* Copy positional arguments */
for (i = 0; i < nargs; i++) {
stack[i] = PyTuple_GET_ITEM(args, i);
Py_INCREF(stack[i]);
}

kwstack = stack + nargs;
pos = i = 0;
/* This loop doesn't support lookup function mutating the dictionary
to change its size. It's a deliberate choice for speed, this function is
called in the performance critical hot code. */
while (PyDict_Next(kwargs, &pos, &key, &value)) {
Py_INCREF(key);
Py_INCREF(value);
PyTuple_SET_ITEM(kwnames, i, key);
kwstack[i] = value;
i++;
}

*p_stack = stack;
*p_kwnames = kwnames;
return 0;
}

static PyObject *
_HPyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)
{
PyTypeObject *tp = Py_TYPE(callable);
/* get vectorcallfunc as in _PyVectorcall_Function, but without
* the _Py_TPFLAGS_HAVE_VECTORCALL check */
cpy_vectorcallfunc func = _HPyType_get_vectorcall_default(tp);
if (func == NULL) {
PyErr_Format(PyExc_TypeError, "'%.200s' object does not support vectorcall",
Py_TYPE(callable)->tp_name);
return NULL;
}

/* Convert arguments & call */
PyObject *const *args;
Py_ssize_t nargs = PyTuple_GET_SIZE(tuple);
PyObject *kwnames;
if (_HPyStack_UnpackDict(tuple, nargs, kwargs, &args, &kwnames) < 0) {
return NULL;
}
PyObject *result = func(callable, args, nargs, kwnames);
Py_ssize_t n = (kwnames != NULL ? PyTuple_GET_SIZE(kwnames) : 0) + nargs;

Py_ssize_t i;
for (i = 0; i < n; i++) {
Py_DECREF(args[i]);
}
PyMem_Free((PyObject **)args);
Py_XDECREF(kwnames);

return result;
}
#endif /* HAVE_VECTORCALL_SUPPORT */

static int
sig2flags(HPyFunc_Signature sig)
Expand All @@ -291,28 +399,6 @@ is_bf_slot(HPyDef *def)
def->slot.slot == HPy_bf_getbuffer || def->slot.slot == HPy_bf_releasebuffer);
}

static inline bool
is_traverse_slot(HPyDef *def)
{
return def->kind == HPyDef_Kind_Slot && def->slot.slot == HPy_tp_traverse;
}

static inline bool
is_destroy_slot(HPyDef *def)
{
return def->kind == HPyDef_Kind_Slot && def->slot.slot == HPy_tp_destroy;
}

static inline bool
is_vectorcall_default_slot(HPyDef *def)
{
#if HAVE_VECTORCALL_SUPPORT
return def->kind == HPyDef_Kind_Slot && def->slot.slot == HPy_tp_vectorcall_default;
#else
return false;
#endif
}

static inline bool
is_slot(HPyDef *def, HPySlot_Slot id)
{
Expand All @@ -328,8 +414,8 @@ HPyDef_count(HPyDef *defs[], HPyDef_Kind kind)
for(int i=0; defs[i] != NULL; i++)
if (defs[i]->kind == kind
&& !is_bf_slot(defs[i])
&& !is_destroy_slot(defs[i])
&& !is_vectorcall_default_slot(defs[i]))
&& !is_slot(defs[i], HPy_tp_destroy)
&& !is_slot(defs[i], HPy_tp_vectorcall_default))
res++;
return res;
}
Expand Down Expand Up @@ -631,13 +717,14 @@ create_slot_defs(HPyType_Spec *hpyspec, HPyType_Extra_t *extra,
&legacy_method_defs, &legacy_member_defs,
&legacy_getset_defs);
bool needs_dealloc = needs_hpytype_dealloc(hpyspec);
#if HAVE_VECTORCALL_SUPPORT
size_t vectorcalloffset = 0;
bool has_tp_new = false;
bool has_tp_call = false;

#if HAVE_VECTORCALL_SUPPORT
bool has_tp_new = false;
#define ADDITIONAL_SLOTS 3
#else
#define ADDITIONAL_SLOTS 1
#define ADDITIONAL_SLOTS 2
#endif
/* This accounts for the sentinel and maybe additional slots that HPy
installs automatically for some reason. For example, in case of the
Expand Down Expand Up @@ -671,12 +758,11 @@ create_slot_defs(HPyType_Spec *hpyspec, HPyType_Extra_t *extra,
HPyDef *src = hpyspec->defines[i];
if (src->kind != HPyDef_Kind_Slot || is_bf_slot(src))
continue;
if (is_destroy_slot(src)) {
if (is_slot(src, HPy_tp_destroy)) {
extra->tp_destroy_impl = (HPyFunc_destroyfunc)src->slot.impl;
continue; /* we don't have a trampoline for tp_destroy */
}
#if HAVE_VECTORCALL_SUPPORT
if (is_vectorcall_default_slot(src)) {
if (is_slot(src, HPy_tp_vectorcall_default)) {
/* Slot 'HPy_tp_vectorcall_default' will add a hidden field to
the type's struct. The field can only be appended which
conflicts with var objects. So, we don't allow this if
Expand All @@ -689,23 +775,24 @@ create_slot_defs(HPyType_Spec *hpyspec, HPyType_Extra_t *extra,
}
// we only need to remember the CPython trampoline
extra->tp_vectorcall_default_trampoline =
(vectorcallfunc)src->slot.cpy_trampoline;
(cpy_vectorcallfunc)src->slot.cpy_trampoline;
/* Adding the hidden field means we increase the CPython type
spec's basic by 'sizeof(vectorcallfunc)'. In case that HPy
type spec's basic size was 0, we now need to adjust the
base_member_offset since that will no longer be inherited
automatically. */
vectorcalloffset = *basicsize;
*basicsize += sizeof(vectorcallfunc);
*basicsize += sizeof(cpy_vectorcallfunc);
continue; /* there is no corresponding C API slot */
}
#if HAVE_VECTORCALL_SUPPORT
if (is_slot(src, HPy_tp_new))
has_tp_new = true;
else if (is_slot(src, HPy_tp_call))
has_tp_call = true;
else
#endif /* HAVE_VECTORCALL_SUPPORT */
if (is_traverse_slot(src)) {
if (is_slot(src, HPy_tp_call))
has_tp_call = true;
else if (is_slot(src, HPy_tp_traverse)) {
extra->tp_traverse_impl = (HPyFunc_traverseproc)src->slot.impl;
/* no 'continue' here: we have a trampoline too */
}
Expand All @@ -715,27 +802,31 @@ create_slot_defs(HPyType_Spec *hpyspec, HPyType_Extra_t *extra,
}
}

#if HAVE_VECTORCALL_SUPPORT
/* If the user did not provide the new function, we need to install a
special new function that will delegate to the inherited tp_new but
additionally sets the default vectorcall function. This is not necessary
if the user provides the new function because he will use 'HPy_New' to
allocate the object which already takes care of that. */
if (vectorcalloffset > 0) {
#if HAVE_VECTORCALL_SUPPORT
if (!has_tp_new) {
additional_slots++;
PyType_Slot *dst = &result[dst_idx++];
dst->slot = Py_tp_new;
dst->pfunc = (void*)hpyobject_new;
}
#endif /* HAVE_VECTORCALL_SUPPORT */
if (!has_tp_call) {
additional_slots++;
PyType_Slot *dst = &result[dst_idx++];
dst->slot = Py_tp_call;
#if HAVE_VECTORCALL_SUPPORT
dst->pfunc = (void*)PyVectorcall_Call;
#else
dst->pfunc = (void*)_HPyVectorcall_Call;
#endif /* HAVE_VECTORCALL_SUPPORT */
}
}
#endif

/* Since the basicsize may be modified depending on special HPy slots, we
defer determination of the base_member_offset to this point. */
Expand Down Expand Up @@ -1578,6 +1669,7 @@ _HPy_HIDDEN HPyType_BuiltinShape ctx_Type_GetBuiltinShape(HPyContext *ctx, HPy h
_HPy_HIDDEN int
ctx_Vectorcall_Set(HPyContext *ctx, HPy h, HPyVectorcall *vectorcall)
{
#if HAVE_VECTORCALL_SUPPORT
PyObject *obj = _h2py(h);
PyTypeObject *tp = Py_TYPE(obj);
if (!PyType_HasFeature(tp, _Py_TPFLAGS_HAVE_VECTORCALL)) {
Expand All @@ -1588,4 +1680,9 @@ ctx_Vectorcall_Set(HPyContext *ctx, HPy h, HPyVectorcall *vectorcall)
}
_HPy_set_vectorcall_func(tp, obj, vectorcall->cpy_trampoline);
return 0;
#else
PyErr_SetString(PyExc_SystemError,
"vectorcall protocol is not supported on this runtime");
return -1;
#endif
}
27 changes: 18 additions & 9 deletions test/test_hpytype.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,8 +1031,12 @@ def test_vectorcall_set(self):
HPy h_point = HPy_New(ctx, cls, &point);
if (HPy_IsNull(h_point))
return HPy_NULL;
if (x < 0)
HPyVectorcall_Set(ctx, h_point, &Point_special_vectorcall);
if (x < 0) {
if (HPyVectorcall_Set(ctx, h_point, &Point_special_vectorcall) < 0) {
HPy_Close(ctx, h_point);
return HPy_NULL;
}
}
point->x = x;
point->y = y;
return h_point;
Expand All @@ -1050,19 +1054,24 @@ def test_vectorcall_set(self):
@EXPORT(vectorcall_set)
@INIT
""")
if self.supports_vectorcall():
pass

# this uses 'Point_vectorcall'
p0 = mod.Point(1, 2)
assert p0(3, 4, 5, factor=2) == 30

# the negative 'x' will cause that 'Point_special_vectorcall' is used
p1 = mod.Point(-1, 2)
assert p1(3, 4, 5, factor=2) == -26
if self.supports_vectorcall():
p1 = mod.Point(-1, 2)
assert p1(3, 4, 5, factor=2) == -26
else:
with pytest.raises(SystemError):
mod.Point(-1, 2)

# error case: setting vectorcall function on object that does not
# implement the vectorcall protocol
# error case: setting vectorcall function on object that does not
# implement the vectorcall protocol
with pytest.raises(TypeError):
with pytest.raises(TypeError if self.supports_vectorcall() else SystemError):
mod.vectorcall_set(object())

def test_vectorcall_var_object(self):
Expand All @@ -1081,8 +1090,8 @@ def test_vectorcall_var_object(self):
.name = "mytest.Dummy",
.itemsize = sizeof(intptr_t),
.flags = HPy_TPFLAGS_DEFAULT,
.defines = Dummy_defines,
@DEFAULT_SHAPE
.defines = Dummy_defines,
};
HPyDef_METH(create_var_type, "create_var_type", HPyFunc_NOARGS)
Expand Down Expand Up @@ -1148,8 +1157,8 @@ def test_vectorcall_legacy(self):
.basicsize = sizeof(FooObject),
.itemsize = sizeof(intptr_t),
.flags = HPy_TPFLAGS_DEFAULT | HPy_TPFLAGS_HAVE_VECTORCALL,
.defines = Foo_defines,
@DEFAULT_SHAPE
.defines = Foo_defines,
};
@EXPORT_TYPE("Foo", Foo_spec)
Expand Down

0 comments on commit 2f6531c

Please sign in to comment.