Skip to content

Commit 9be7e7b

Browse files
committed
Add _PyObject_FastCall()
Issue python#27128: Add _PyObject_FastCall(), a new calling convention avoiding a temporary tuple to pass positional parameters in most cases, but create a temporary tuple if needed (ex: for the tp_call slot). The API is prepared to support keyword parameters, but the full implementation will come later (_PyFunction_FastCall() doesn't support keyword parameters yet). Add also: * _PyStack_AsTuple() helper function: convert a "stack" of parameters to a tuple. * _PyCFunction_FastCall(): fast call implementation for C functions * _PyFunction_FastCall(): fast call implementation for Python functions
1 parent fa46aa7 commit 9be7e7b

File tree

6 files changed

+312
-47
lines changed

6 files changed

+312
-47
lines changed

Include/abstract.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,26 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
267267
PyObject *args, PyObject *kw);
268268

269269
#ifndef Py_LIMITED_API
270+
PyAPI_FUNC(PyObject*) _PyStack_AsTuple(PyObject **stack,
271+
Py_ssize_t nargs);
272+
273+
/* Call the callable object func with the "fast call" calling convention:
274+
args is a C array for positional parameters (nargs is the number of
275+
positional paramater), kwargs is a dictionary for keyword parameters.
276+
277+
If nargs is equal to zero, args can be NULL. kwargs can be NULL.
278+
nargs must be greater or equal to zero.
279+
280+
Return the result on success. Raise an exception on return NULL on
281+
error. */
282+
PyAPI_FUNC(PyObject *) _PyObject_FastCall(PyObject *func,
283+
PyObject **args, int nargs,
284+
PyObject *kwargs);
285+
270286
PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *func,
271287
PyObject *result,
272288
const char *where);
273-
#endif
289+
#endif /* Py_LIMITED_API */
274290

275291
/*
276292
Call a callable Python object, callable_object, with

Include/funcobject.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ PyAPI_FUNC(int) PyFunction_SetClosure(PyObject *, PyObject *);
5858
PyAPI_FUNC(PyObject *) PyFunction_GetAnnotations(PyObject *);
5959
PyAPI_FUNC(int) PyFunction_SetAnnotations(PyObject *, PyObject *);
6060

61+
#ifndef Py_LIMITED_API
62+
PyAPI_FUNC(PyObject *) _PyFunction_FastCall(
63+
PyObject *func,
64+
PyObject **args, int nargs,
65+
PyObject *kwargs);
66+
#endif
67+
6168
/* Macros for direct access to these values. Type checks are *not*
6269
done, so use with care. */
6370
#define PyFunction_GET_CODE(func) \

Include/methodobject.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *);
3737
#endif
3838
PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *);
3939

40+
#ifndef Py_LIMITED_API
41+
PyAPI_FUNC(PyObject *) _PyCFunction_FastCall(PyObject *func,
42+
PyObject **args, int nargs,
43+
PyObject *kwargs);
44+
#endif
45+
4046
struct PyMethodDef {
4147
const char *ml_name; /* The name of the built-in function/method */
4248
PyCFunction ml_meth; /* The C function that implements it */

Objects/abstract.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,6 +2193,82 @@ PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
21932193
return _Py_CheckFunctionResult(func, result, NULL);
21942194
}
21952195

2196+
PyObject*
2197+
_PyStack_AsTuple(PyObject **stack, Py_ssize_t nargs)
2198+
{
2199+
PyObject *args;
2200+
Py_ssize_t i;
2201+
2202+
args = PyTuple_New(nargs);
2203+
if (args == NULL) {
2204+
return NULL;
2205+
}
2206+
2207+
for (i=0; i < nargs; i++) {
2208+
PyObject *item = stack[i];
2209+
Py_INCREF(item);
2210+
PyTuple_SET_ITEM(args, i, item);
2211+
}
2212+
2213+
return args;
2214+
}
2215+
2216+
PyObject *
2217+
_PyObject_FastCall(PyObject *func, PyObject **args, int nargs, PyObject *kwargs)
2218+
{
2219+
ternaryfunc call;
2220+
PyObject *result = NULL;
2221+
2222+
/* _PyObject_FastCall() must not be called with an exception set,
2223+
because it may clear it (directly or indirectly) and so the
2224+
caller loses its exception */
2225+
assert(!PyErr_Occurred());
2226+
2227+
assert(func != NULL);
2228+
assert(nargs >= 0);
2229+
assert(nargs == 0 || args != NULL);
2230+
/* issue #27128: support for keywords will come later:
2231+
_PyFunction_FastCall() doesn't support keyword arguments yet */
2232+
assert(kwargs == NULL);
2233+
2234+
if (Py_EnterRecursiveCall(" while calling a Python object")) {
2235+
return NULL;
2236+
}
2237+
2238+
if (PyFunction_Check(func)) {
2239+
result = _PyFunction_FastCall(func, args, nargs, kwargs);
2240+
}
2241+
else if (PyCFunction_Check(func)) {
2242+
result = _PyCFunction_FastCall(func, args, nargs, kwargs);
2243+
}
2244+
else {
2245+
PyObject *tuple;
2246+
2247+
/* Slow-path: build a temporary tuple */
2248+
call = func->ob_type->tp_call;
2249+
if (call == NULL) {
2250+
PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
2251+
func->ob_type->tp_name);
2252+
goto exit;
2253+
}
2254+
2255+
tuple = _PyStack_AsTuple(args, nargs);
2256+
if (tuple == NULL) {
2257+
goto exit;
2258+
}
2259+
2260+
result = (*call)(func, tuple, kwargs);
2261+
Py_DECREF(tuple);
2262+
}
2263+
2264+
result = _Py_CheckFunctionResult(func, result, NULL);
2265+
2266+
exit:
2267+
Py_LeaveRecursiveCall();
2268+
2269+
return result;
2270+
}
2271+
21962272
static PyObject*
21972273
call_function_tail(PyObject *callable, PyObject *args)
21982274
{

Objects/methodobject.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,99 @@ PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
145145
return _Py_CheckFunctionResult(func, res, NULL);
146146
}
147147

148+
PyObject *
149+
_PyCFunction_FastCall(PyObject *func_obj, PyObject **args, int nargs,
150+
PyObject *kwargs)
151+
{
152+
PyCFunctionObject* func = (PyCFunctionObject*)func_obj;
153+
PyCFunction meth = PyCFunction_GET_FUNCTION(func);
154+
PyObject *self = PyCFunction_GET_SELF(func);
155+
PyObject *result;
156+
int flags;
157+
158+
/* _PyCFunction_FastCall() must not be called with an exception set,
159+
because it may clear it (directly or indirectly) and so the
160+
caller loses its exception */
161+
assert(!PyErr_Occurred());
162+
163+
flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
164+
165+
switch (flags)
166+
{
167+
case METH_NOARGS:
168+
if (kwargs != NULL && PyDict_Size(kwargs) != 0) {
169+
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
170+
func->m_ml->ml_name);
171+
return NULL;
172+
}
173+
174+
if (nargs != 0) {
175+
PyErr_Format(PyExc_TypeError,
176+
"%.200s() takes no arguments (%zd given)",
177+
func->m_ml->ml_name, nargs);
178+
return NULL;
179+
}
180+
181+
result = (*meth) (self, NULL);
182+
break;
183+
184+
case METH_O:
185+
if (kwargs != NULL && PyDict_Size(kwargs) != 0) {
186+
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
187+
func->m_ml->ml_name);
188+
return NULL;
189+
}
190+
191+
if (nargs != 1) {
192+
PyErr_Format(PyExc_TypeError,
193+
"%.200s() takes exactly one argument (%zd given)",
194+
func->m_ml->ml_name, nargs);
195+
return NULL;
196+
}
197+
198+
result = (*meth) (self, args[0]);
199+
break;
200+
201+
case METH_VARARGS:
202+
case METH_VARARGS | METH_KEYWORDS:
203+
{
204+
/* Slow-path: create a temporary tuple */
205+
PyObject *tuple;
206+
207+
if (!(flags & METH_KEYWORDS) && kwargs != NULL && PyDict_Size(kwargs) != 0) {
208+
PyErr_Format(PyExc_TypeError,
209+
"%.200s() takes no keyword arguments",
210+
func->m_ml->ml_name);
211+
return NULL;
212+
}
213+
214+
tuple = _PyStack_AsTuple(args, nargs);
215+
if (tuple == NULL) {
216+
return NULL;
217+
}
218+
219+
if (flags & METH_KEYWORDS) {
220+
result = (*(PyCFunctionWithKeywords)meth) (self, tuple, kwargs);
221+
}
222+
else {
223+
result = (*meth) (self, tuple);
224+
}
225+
Py_DECREF(tuple);
226+
break;
227+
}
228+
229+
default:
230+
PyErr_SetString(PyExc_SystemError,
231+
"Bad call flags in PyCFunction_Call. "
232+
"METH_OLDARGS is no longer supported!");
233+
return NULL;
234+
}
235+
236+
result = _Py_CheckFunctionResult(func_obj, result, NULL);
237+
238+
return result;
239+
}
240+
148241
/* Methods (the standard built-in methods, that is) */
149242

150243
static void

0 commit comments

Comments
 (0)