Skip to content

bpo-38787: C API for module state access from extension methods (PEP 573) #19936

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

Merged
merged 9 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Add PyCMethod_* and machinery to call it
  • Loading branch information
Marcel Plch authored and encukou committed May 5, 2020
commit 856f0a31701005b29988098de79983271ed40845
12 changes: 12 additions & 0 deletions Include/cpython/methodobject.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#ifndef Py_CPYTHON_METHODOBJECT_H
# error "this header file must not be included directly"
#endif

PyAPI_DATA(PyTypeObject) PyCMethod_Type;

/* Macros for direct access to these values. Type checks are *not*
done, so use with care. */
#define PyCFunction_GET_FUNCTION(func) \
Expand All @@ -10,6 +13,10 @@
NULL : ((PyCFunctionObject *)func) -> m_self)
#define PyCFunction_GET_FLAGS(func) \
(((PyCFunctionObject *)func) -> m_ml -> ml_flags)
#define PyCFunction_GET_CLASS(func) \
(((PyCFunctionObject *)func) -> m_ml -> ml_flags & METH_METHOD ? \
((PyCMethodObject *)func) -> mm_class : NULL)

typedef struct {
PyObject_HEAD
PyMethodDef *m_ml; /* Description of the C function to call */
Expand All @@ -18,3 +25,8 @@ typedef struct {
PyObject *m_weakreflist; /* List of weak references */
vectorcallfunc vectorcall;
} PyCFunctionObject;

typedef struct {
PyCFunctionObject func;
PyTypeObject *mm_class; /* Class that defines this method */
} PyCMethodObject;
24 changes: 23 additions & 1 deletion Include/methodobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extern "C" {

PyAPI_DATA(PyTypeObject) PyCFunction_Type;

#define PyCFunction_Check(op) Py_IS_TYPE(op, &PyCFunction_Type)
#define PyCFunction_Check(op) (Py_IS_TYPE(op, &PyCFunction_Type) || (PyType_IsSubtype(Py_TYPE(op), &PyCFunction_Type)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the PyObject_TypeCheck() macro.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I think we need a PyCFunction_CheckExact() macro now if we allow subtypes, to distinguish type checks that guard struct access (Check) from those that optimise for known behaviour (CheckExact).

That probably means that we'll have to look through all usages in CPython to see which is intended. 8-]
This would generally be great to have for Cython, because then I can make Cython's own function type inherit from PyCFunction_Type in Py3.9+. Hmm, guess I should volunteer to do the work here…

Also, the usual pattern for PyXYZ_Check() functions is to check for a type flag, rather than running a subtype check. Should we add one for PyCFunction, too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there weren't all that many such type checks. I pushed #20024.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the usual pattern for PyXYZ_Check() functions is to check for a type flag, rather than running a subtype check. Should we add one for PyCFunction, too?

No. Subclasses of PyCFunction are still quite special.
The C-level check should really be used only for very specific optimizations (or the check for print in order to give a helpful meessage, I guess). To check for "native callables", look for vectorcall.


typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
typedef PyObject *(*_PyCFunctionFast) (PyObject *, PyObject *const *, Py_ssize_t);
Expand All @@ -22,6 +22,9 @@ typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *,
typedef PyObject *(*_PyCFunctionFastWithKeywords) (PyObject *,
PyObject *const *, Py_ssize_t,
PyObject *);
typedef PyObject *(*PyCMethod)(PyObject *, PyTypeObject *, PyObject *const *,
size_t, PyObject *);

PyAPI_FUNC(PyCFunction) PyCFunction_GetFunction(PyObject *);
PyAPI_FUNC(PyObject *) PyCFunction_GetSelf(PyObject *);
PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *);
Expand All @@ -41,6 +44,13 @@ typedef struct PyMethodDef PyMethodDef;
PyAPI_FUNC(PyObject *) PyCFunction_NewEx(PyMethodDef *, PyObject *,
PyObject *);

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03090000
#define PyCFunction_NewEx(ML, SELF, MOD) PyCMethod_New((ML), (SELF), (MOD), NULL)
PyAPI_FUNC(PyObject *) PyCMethod_New(PyMethodDef *, PyObject *,
PyObject *, PyTypeObject *);
#endif


/* Flag passed to newmethodobject */
/* #define METH_OLDARGS 0x0000 -- unsupported now */
#define METH_VARARGS 0x0001
Expand Down Expand Up @@ -73,6 +83,18 @@ PyAPI_FUNC(PyObject *) PyCFunction_NewEx(PyMethodDef *, PyObject *,
#define METH_STACKLESS 0x0000
#endif

/* METH_METHOD means the function stores an
* additional reference to the class that defines it;
* both self and class are passed to it.
* It uses PyCMethodObject instead of PyCFunctionObject.
* May not be combined with METH_NOARGS, METH_O, METH_CLASS or METH_STATIC.
*/

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03090000
#define METH_METHOD 0x0200
#endif


#ifndef Py_LIMITED_API

#define Py_CPYTHON_METHODOBJECT_H
Expand Down
47 changes: 44 additions & 3 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)
((PyTypeObject *)type)->tp_name);
return NULL;
}
return PyCFunction_NewEx(descr->d_method, type, NULL);
PyTypeObject *cls = NULL;
if (descr->d_method->ml_flags & METH_METHOD) {
cls = descr->d_common.d_type;
}
return PyCMethod_New(descr->d_method, type, NULL, cls);
}

static PyObject *
Expand All @@ -137,7 +141,19 @@ method_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)

if (descr_check((PyDescrObject *)descr, obj, &res))
return res;
return PyCFunction_NewEx(descr->d_method, obj, NULL);
if (descr->d_method->ml_flags & METH_METHOD) {
if (PyType_Check(type)) {
return PyCMethod_New(descr->d_method, obj, NULL, descr->d_common.d_type);
} else {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' needs a type, not '%s', as arg 2",
descr_name((PyDescrObject *)descr),
Py_TYPE(type)->tp_name);
return NULL;
}
} else {
return PyCFunction_NewEx(descr->d_method, obj, NULL);
}
}

static PyObject *
Expand Down Expand Up @@ -335,6 +351,27 @@ method_vectorcall_VARARGS_KEYWORDS(
return result;
}

static PyObject *
method_vectorcall_FASTCALL_KEYWORDS_METHOD(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
PyThreadState *tstate = _PyThreadState_GET();
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (method_check_args(func, args, nargs, NULL)) {
return NULL;
}
NULL;
PyCMethod meth = (PyCMethod) method_enter_call(tstate, func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(args[0],
((PyMethodDescrObject *)func)->d_common.d_type,
args+1, nargs-1, kwnames);
Py_LeaveRecursiveCall();
return result;
}

static PyObject *
method_vectorcall_FASTCALL(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
Expand Down Expand Up @@ -868,7 +905,8 @@ PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
{
/* Figure out correct vectorcall function to use */
vectorcallfunc vectorcall;
switch (method->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))
switch (method->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS |
METH_O | METH_KEYWORDS | METH_METHOD))
{
case METH_VARARGS:
vectorcall = method_vectorcall_VARARGS;
Expand All @@ -888,6 +926,9 @@ PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
case METH_O:
vectorcall = method_vectorcall_O;
break;
case METH_METHOD | METH_FASTCALL | METH_KEYWORDS:
vectorcall = method_vectorcall_FASTCALL_KEYWORDS_METHOD;
break;
default:
PyErr_Format(PyExc_SystemError,
"%s() method: bad call flags", method->ml_name);
Expand Down
83 changes: 78 additions & 5 deletions Objects/methodobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@

/* undefine macro trampoline to PyCFunction_NewEx */
#undef PyCFunction_New
/* undefine macro trampoline to PyCMethod_New */
#undef PyCFunction_NewEx

/* Forward declarations */
static PyObject * cfunction_vectorcall_FASTCALL(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
static PyObject * cfunction_vectorcall_FASTCALL_KEYWORDS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
static PyObject * cfunction_vectorcall_FASTCALL_KEYWORDS_METHOD(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
static PyObject * cfunction_vectorcall_NOARGS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames);
static PyObject * cfunction_vectorcall_O(
Expand All @@ -32,10 +36,17 @@ PyCFunction_New(PyMethodDef *ml, PyObject *self)

PyObject *
PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
{
return PyCMethod_New(ml, self, module, NULL);
}

PyObject *
PyCMethod_New(PyMethodDef *ml, PyObject *self, PyObject *module, PyTypeObject *cls)
{
/* Figure out correct vectorcall function to use */
vectorcallfunc vectorcall;
switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))
switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS |
METH_O | METH_KEYWORDS | METH_METHOD))
{
case METH_VARARGS:
case METH_VARARGS | METH_KEYWORDS:
Expand All @@ -55,17 +66,44 @@ PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
case METH_O:
vectorcall = cfunction_vectorcall_O;
break;
case METH_METHOD | METH_FASTCALL | METH_KEYWORDS:
vectorcall = cfunction_vectorcall_FASTCALL_KEYWORDS_METHOD;
break;
default:
PyErr_Format(PyExc_SystemError,
"%s() method: bad call flags", ml->ml_name);
return NULL;
}

PyCFunctionObject *op =
PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);
if (op == NULL) {
return NULL;
PyCFunctionObject *op = NULL;

if (ml->ml_flags & METH_METHOD) {
if (!cls) {
PyErr_SetString(PyExc_SystemError,
"attempting to create PyCMethod with a METH_METHOD "
"flag but no class");
return NULL;
}
PyCMethodObject *om = PyObject_GC_New(PyCMethodObject, &PyCMethod_Type);
if (om == NULL) {
return NULL;
}
Py_INCREF(cls);
om->mm_class = cls;
op = (PyCFunctionObject *)om;
} else {
if (cls) {
PyErr_SetString(PyExc_SystemError,
"attempting to create PyCFunction with class "
"but no METH_METHOD flag");
return NULL;
}
op = PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);
if (op == NULL) {
return NULL;
}
}

op->m_weakreflist = NULL;
op->m_ml = ml;
Py_XINCREF(self);
Expand Down Expand Up @@ -107,6 +145,16 @@ PyCFunction_GetFlags(PyObject *op)
return PyCFunction_GET_FLAGS(op);
}

PyTypeObject *
PyCMethod_GetClass(PyObject *op)
{
if (!PyCFunction_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
return PyCFunction_GET_CLASS(op);
}

/* Methods (the standard built-in methods, that is) */

static void
Expand All @@ -118,6 +166,7 @@ meth_dealloc(PyCFunctionObject *m)
}
Py_XDECREF(m->m_self);
Py_XDECREF(m->m_module);
Py_XDECREF(PyCFunction_GET_CLASS(m));
PyObject_GC_Del(m);
}

Expand Down Expand Up @@ -196,6 +245,7 @@ meth_traverse(PyCFunctionObject *m, visitproc visit, void *arg)
{
Py_VISIT(m->m_self);
Py_VISIT(m->m_module);
Py_VISIT(PyCFunction_GET_CLASS(m));
return 0;
}

Expand Down Expand Up @@ -314,6 +364,13 @@ PyTypeObject PyCFunction_Type = {
0, /* tp_dict */
};

PyTypeObject PyCMethod_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
.tp_name = "builtin_method",
.tp_basicsize = sizeof(PyCMethodObject),
.tp_base = &PyCFunction_Type,
};

/* Vectorcall functions for each of the PyCFunction calling conventions,
* except for METH_VARARGS (possibly combined with METH_KEYWORDS) which
* doesn't use vectorcall.
Expand Down Expand Up @@ -385,6 +442,22 @@ cfunction_vectorcall_FASTCALL_KEYWORDS(
return result;
}

static PyObject *
cfunction_vectorcall_FASTCALL_KEYWORDS_METHOD(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
{
PyThreadState *tstate = _PyThreadState_GET();
PyTypeObject *cls = PyCFunction_GET_CLASS(func);
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
PyCMethod meth = (PyCMethod)cfunction_enter_call(tstate, func);
if (meth == NULL) {
return NULL;
}
PyObject *result = meth(PyCFunction_GET_SELF(func), cls, args, nargs, kwnames);
_Py_LeaveRecursiveCall(tstate);
return result;
}

static PyObject *
cfunction_vectorcall_NOARGS(
PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
Expand Down
1 change: 1 addition & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1789,6 +1789,7 @@ _PyTypes_Init(void)
INIT_TYPE(&PyCode_Type, "code");
INIT_TYPE(&PyFrame_Type, "frame");
INIT_TYPE(&PyCFunction_Type, "builtin function");
INIT_TYPE(&PyCMethod_Type, "builtin method");
INIT_TYPE(&PyMethod_Type, "method");
INIT_TYPE(&PyFunction_Type, "function");
INIT_TYPE(&PyDictProxy_Type, "dict proxy");
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy