Skip to content

Commit a9efb2f

Browse files
committed
Add METH_FASTCALL calling convention
Issue python#27810: Add a new calling convention for C functions: PyObject* func(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames); Where args is a C array of positional arguments followed by values of keyword arguments. nargs is the number of positional arguments, kwnames are keys of keyword arguments. kwnames can be NULL.
1 parent 78601a3 commit a9efb2f

File tree

5 files changed

+102
-1
lines changed

5 files changed

+102
-1
lines changed

Include/abstract.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,22 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
277277
PyObject *kwnames,
278278
PyObject *func);
279279

280+
/* Convert (args, nargs, kwargs) into a (stack, nargs, kwnames).
281+
282+
Return a new stack which should be released by PyMem_Free(), or return
283+
args unchanged if kwargs is NULL or an empty dictionary.
284+
285+
The stack uses borrowed references.
286+
287+
The type of keyword keys is not checked, these checks should be done
288+
later (ex: _PyArg_ParseStack). */
289+
PyAPI_FUNC(PyObject **) _PyStack_UnpackDict(
290+
PyObject **args,
291+
Py_ssize_t nargs,
292+
PyObject *kwargs,
293+
PyObject **kwnames,
294+
PyObject *func);
295+
280296
/* Call the callable object func with the "fast call" calling convention:
281297
args is a C array for positional arguments (nargs is the number of
282298
positional arguments), kwargs is a dictionary for keyword arguments.

Include/methodobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ PyAPI_DATA(PyTypeObject) PyCFunction_Type;
1616
#define PyCFunction_Check(op) (Py_TYPE(op) == &PyCFunction_Type)
1717

1818
typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
19+
typedef PyObject *(*_PyCFunctionFast) (PyObject *self, PyObject **args,
20+
Py_ssize_t nargs, PyObject *kwnames);
1921
typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *,
2022
PyObject *);
2123
typedef PyObject *(*PyNoArgsFunction)(PyObject *);
@@ -83,6 +85,8 @@ PyAPI_FUNC(PyObject *) PyCFunction_NewEx(PyMethodDef *, PyObject *,
8385

8486
#define METH_COEXIST 0x0040
8587

88+
#define METH_FASTCALL 0x0080
89+
8690
#ifndef Py_LIMITED_API
8791
typedef struct {
8892
PyObject_HEAD

Objects/abstract.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,6 +2403,62 @@ _PyStack_AsDict(PyObject **values, Py_ssize_t nkwargs, PyObject *kwnames,
24032403
return kwdict;
24042404
}
24052405

2406+
PyObject **
2407+
_PyStack_UnpackDict(PyObject **args, Py_ssize_t nargs, PyObject *kwargs,
2408+
PyObject **p_kwnames, PyObject *func)
2409+
{
2410+
PyObject **stack, **kwstack;
2411+
Py_ssize_t nkwargs;
2412+
Py_ssize_t pos, i;
2413+
PyObject *key, *value;
2414+
PyObject *kwnames;
2415+
2416+
assert(nargs >= 0);
2417+
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
2418+
2419+
nkwargs = (kwargs != NULL) ? PyDict_Size(kwargs) : 0;
2420+
if (!nkwargs) {
2421+
*p_kwnames = NULL;
2422+
return args;
2423+
}
2424+
2425+
if ((size_t)nargs > PY_SSIZE_T_MAX / sizeof(stack[0]) - (size_t)nkwargs) {
2426+
PyErr_NoMemory();
2427+
return NULL;
2428+
}
2429+
2430+
stack = PyMem_Malloc((nargs + nkwargs) * sizeof(stack[0]));
2431+
if (stack == NULL) {
2432+
PyErr_NoMemory();
2433+
return NULL;
2434+
}
2435+
2436+
kwnames = PyTuple_New(nkwargs);
2437+
if (kwnames == NULL) {
2438+
PyMem_Free(stack);
2439+
return NULL;
2440+
}
2441+
2442+
/* Copy position arguments (borrowed references) */
2443+
Py_MEMCPY(stack, args, nargs * sizeof(stack[0]));
2444+
2445+
kwstack = stack + nargs;
2446+
pos = i = 0;
2447+
/* This loop doesn't support lookup function mutating the dictionary
2448+
to change its size. It's a deliberate choice for speed, this function is
2449+
called in the performance critical hot code. */
2450+
while (PyDict_Next(kwargs, &pos, &key, &value)) {
2451+
Py_INCREF(key);
2452+
PyTuple_SET_ITEM(kwnames, i, key);
2453+
/* The stack contains borrowed references */
2454+
kwstack[i] = value;
2455+
i++;
2456+
}
2457+
2458+
*p_kwnames = kwnames;
2459+
return stack;
2460+
}
2461+
24062462
PyObject *
24072463
_PyObject_FastCallKeywords(PyObject *func, PyObject **stack, Py_ssize_t nargs,
24082464
PyObject *kwnames)

Objects/methodobject.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
9797
if (flags == (METH_VARARGS | METH_KEYWORDS)) {
9898
res = (*(PyCFunctionWithKeywords)meth)(self, args, kwds);
9999
}
100+
else if (flags == METH_FASTCALL) {
101+
PyObject **stack = &PyTuple_GET_ITEM(args, 0);
102+
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
103+
res = _PyCFunction_FastCallDict(func, stack, nargs, kwds);
104+
}
100105
else {
101106
if (kwds != NULL && PyDict_Size(kwds) != 0) {
102107
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
@@ -232,6 +237,25 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
232237
break;
233238
}
234239

240+
case METH_FASTCALL:
241+
{
242+
PyObject **stack;
243+
PyObject *kwnames;
244+
_PyCFunctionFast fastmeth = (_PyCFunctionFast)meth;
245+
246+
stack = _PyStack_UnpackDict(args, nargs, kwargs, &kwnames, func_obj);
247+
if (stack == NULL) {
248+
return NULL;
249+
}
250+
251+
result = (*fastmeth) (self, stack, nargs, kwnames);
252+
if (stack != args) {
253+
PyMem_Free(stack);
254+
}
255+
Py_XDECREF(kwnames);
256+
break;
257+
}
258+
235259
default:
236260
PyErr_SetString(PyExc_SystemError,
237261
"Bad call flags in PyCFunction_Call. "

Python/getargs.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1992,8 +1992,9 @@ vgetargskeywordsfast(PyObject *args, PyObject *keywords,
19921992
return cleanreturn(0, &freelist);
19931993
}
19941994
}
1995-
else if (i < nargs)
1995+
else if (i < nargs) {
19961996
current_arg = PyTuple_GET_ITEM(args, i);
1997+
}
19971998

19981999
if (current_arg) {
19992000
msg = convertitem(current_arg, &format, p_va, flags,

0 commit comments

Comments
 (0)