Skip to content

Commit 577e1f8

Browse files
committed
Add _PyObject_FastCallKeywords()
Issue #27830: Similar to _PyObject_FastCallDict(), but keyword arguments are also passed in the same C array than positional arguments, rather than being passed as a Python dict.
1 parent 53868aa commit 577e1f8

File tree

4 files changed

+116
-9
lines changed

4 files changed

+116
-9
lines changed

Include/abstract.h

+17
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,23 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
292292
#define _PyObject_CallArg1(func, arg) \
293293
_PyObject_FastCall((func), &(arg), 1)
294294

295+
/* Call the callable object func with the "fast call" calling convention:
296+
args is a C array for positional arguments followed by (key, value)
297+
pairs for keyword arguments.
298+
299+
nargs is the number of positional parameters at the beginning of stack.
300+
nkwargs is the number of (key, value) pairs at the end of stack.
301+
302+
If nargs and nkwargs are equal to zero, stack can be NULL.
303+
304+
Return the result on success. Raise an exception and return NULL on
305+
error. */
306+
PyAPI_FUNC(PyObject *) _PyObject_FastCallKeywords(
307+
PyObject *func,
308+
PyObject **stack,
309+
Py_ssize_t nargs,
310+
Py_ssize_t nkwargs);
311+
295312
PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *func,
296313
PyObject *result,
297314
const char *where);

Include/funcobject.h

+6
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ PyAPI_FUNC(PyObject *) _PyFunction_FastCallDict(
6464
PyObject **args,
6565
Py_ssize_t nargs,
6666
PyObject *kwargs);
67+
68+
PyAPI_FUNC(PyObject *) _PyFunction_FastCallKeywords(
69+
PyObject *func,
70+
PyObject **stack,
71+
Py_ssize_t nargs,
72+
Py_ssize_t nkwargs);
6773
#endif
6874

6975
/* Macros for direct access to these values. Type checks are *not*

Objects/abstract.c

+79
Original file line numberDiff line numberDiff line change
@@ -2309,6 +2309,85 @@ _PyObject_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs,
23092309
return result;
23102310
}
23112311

2312+
static PyObject *
2313+
_PyStack_AsDict(PyObject **stack, Py_ssize_t nkwargs, PyObject *func)
2314+
{
2315+
PyObject *kwdict;
2316+
2317+
kwdict = PyDict_New();
2318+
if (kwdict == NULL) {
2319+
return NULL;
2320+
}
2321+
2322+
while (--nkwargs >= 0) {
2323+
int err;
2324+
PyObject *key = *stack++;
2325+
PyObject *value = *stack++;
2326+
if (PyDict_GetItem(kwdict, key) != NULL) {
2327+
PyErr_Format(PyExc_TypeError,
2328+
"%.200s%s got multiple values "
2329+
"for keyword argument '%U'",
2330+
PyEval_GetFuncName(func),
2331+
PyEval_GetFuncDesc(func),
2332+
key);
2333+
Py_DECREF(kwdict);
2334+
return NULL;
2335+
}
2336+
2337+
err = PyDict_SetItem(kwdict, key, value);
2338+
if (err) {
2339+
Py_DECREF(kwdict);
2340+
return NULL;
2341+
}
2342+
}
2343+
return kwdict;
2344+
}
2345+
2346+
PyObject *
2347+
_PyObject_FastCallKeywords(PyObject *func, PyObject **stack, Py_ssize_t nargs,
2348+
Py_ssize_t nkwargs)
2349+
{
2350+
PyObject *args, *kwdict, *result;
2351+
2352+
/* _PyObject_FastCallKeywords() must not be called with an exception set,
2353+
because it may clear it (directly or indirectly) and so the
2354+
caller loses its exception */
2355+
assert(!PyErr_Occurred());
2356+
2357+
assert(func != NULL);
2358+
assert(nargs >= 0);
2359+
assert(nkwargs >= 0);
2360+
assert((nargs == 0 && nkwargs == 0) || stack != NULL);
2361+
2362+
if (PyFunction_Check(func)) {
2363+
/* Fast-path: avoid temporary tuple or dict */
2364+
return _PyFunction_FastCallKeywords(func, stack, nargs, nkwargs);
2365+
}
2366+
2367+
if (PyCFunction_Check(func) && nkwargs == 0) {
2368+
return _PyCFunction_FastCallDict(func, args, nargs, NULL);
2369+
}
2370+
2371+
/* Slow-path: build temporary tuple and/or dict */
2372+
args = _PyStack_AsTuple(stack, nargs);
2373+
2374+
if (nkwargs > 0) {
2375+
kwdict = _PyStack_AsDict(stack + nargs, nkwargs, func);
2376+
if (kwdict == NULL) {
2377+
Py_DECREF(args);
2378+
return NULL;
2379+
}
2380+
}
2381+
else {
2382+
kwdict = NULL;
2383+
}
2384+
2385+
result = PyObject_Call(func, args, kwdict);
2386+
Py_DECREF(args);
2387+
Py_XDECREF(kwdict);
2388+
return result;
2389+
}
2390+
23122391
static PyObject*
23132392
call_function_tail(PyObject *callable, PyObject *args)
23142393
{

Python/ceval.c

+14-9
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ static PyObject * call_function(PyObject ***, int, uint64*, uint64*);
113113
#else
114114
static PyObject * call_function(PyObject ***, int);
115115
#endif
116-
static PyObject * fast_function(PyObject *, PyObject **, Py_ssize_t, Py_ssize_t);
117116
static PyObject * do_call(PyObject *, PyObject ***, Py_ssize_t, Py_ssize_t);
118117
static PyObject * ext_do_call(PyObject *, PyObject ***, int, Py_ssize_t, Py_ssize_t);
119118
static PyObject * update_keyword_args(PyObject *, Py_ssize_t, PyObject ***,
@@ -4767,7 +4766,7 @@ call_function(PyObject ***pp_stack, int oparg
47674766
}
47684767
READ_TIMESTAMP(*pintr0);
47694768
if (PyFunction_Check(func)) {
4770-
x = fast_function(func, (*pp_stack) - n, nargs, nkwargs);
4769+
x = _PyFunction_FastCallKeywords(func, (*pp_stack) - n, nargs, nkwargs);
47714770
}
47724771
else {
47734772
x = do_call(func, pp_stack, nargs, nkwargs);
@@ -4780,7 +4779,7 @@ call_function(PyObject ***pp_stack, int oparg
47804779

47814780
/* Clear the stack of the function object. Also removes
47824781
the arguments in case they weren't consumed already
4783-
(fast_function() and err_args() leave them on the stack).
4782+
(_PyFunction_FastCallKeywords() and err_args() leave them on the stack).
47844783
*/
47854784
while ((*pp_stack) > pfunc) {
47864785
w = EXT_POP(*pp_stack);
@@ -4792,7 +4791,7 @@ call_function(PyObject ***pp_stack, int oparg
47924791
return x;
47934792
}
47944793

4795-
/* The fast_function() function optimize calls for which no argument
4794+
/* The _PyFunction_FastCallKeywords() function optimize calls for which no argument
47964795
tuple is necessary; the objects are passed directly from the stack.
47974796
For the simplest case -- a function that takes only positional
47984797
arguments and is called with only positional arguments -- it
@@ -4840,8 +4839,9 @@ _PyFunction_FastCallNoKw(PyCodeObject *co, PyObject **args, Py_ssize_t nargs,
48404839

48414840
/* Similar to _PyFunction_FastCall() but keywords are passed a (key, value)
48424841
pairs in stack */
4843-
static PyObject *
4844-
fast_function(PyObject *func, PyObject **stack, Py_ssize_t nargs, Py_ssize_t nkwargs)
4842+
PyObject *
4843+
_PyFunction_FastCallKeywords(PyObject *func, PyObject **stack,
4844+
Py_ssize_t nargs, Py_ssize_t nkwargs)
48454845
{
48464846
PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
48474847
PyObject *globals = PyFunction_GET_GLOBALS(func);
@@ -4850,6 +4850,11 @@ fast_function(PyObject *func, PyObject **stack, Py_ssize_t nargs, Py_ssize_t nkw
48504850
PyObject **d;
48514851
int nd;
48524852

4853+
assert(func != NULL);
4854+
assert(nargs >= 0);
4855+
assert(nkwargs >= 0);
4856+
assert((nargs == 0 && nkwargs == 0) || stack != NULL);
4857+
48534858
PCALL(PCALL_FUNCTION);
48544859
PCALL(PCALL_FAST_FUNCTION);
48554860

@@ -4902,14 +4907,14 @@ _PyFunction_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs,
49024907
Py_ssize_t nd, nk;
49034908
PyObject *result;
49044909

4905-
PCALL(PCALL_FUNCTION);
4906-
PCALL(PCALL_FAST_FUNCTION);
4907-
49084910
assert(func != NULL);
49094911
assert(nargs >= 0);
49104912
assert(nargs == 0 || args != NULL);
49114913
assert(kwargs == NULL || PyDict_Check(kwargs));
49124914

4915+
PCALL(PCALL_FUNCTION);
4916+
PCALL(PCALL_FAST_FUNCTION);
4917+
49134918
if (co->co_kwonlyargcount == 0 &&
49144919
(kwargs == NULL || PyDict_Size(kwargs) == 0) &&
49154920
co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))

0 commit comments

Comments
 (0)