Skip to content

Commit 3163e68

Browse files
bpo-44525: Specialize CALL_FUNCTION for C function calls (GH-26934)
1 parent 3592980 commit 3163e68

File tree

7 files changed

+365
-50
lines changed

7 files changed

+365
-50
lines changed

Include/internal/pycore_code.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ int _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *na
309309
int _Py_Specialize_BinarySubscr(PyObject *sub, PyObject *container, _Py_CODEUNIT *instr);
310310
int _Py_Specialize_BinaryAdd(PyObject *left, PyObject *right, _Py_CODEUNIT *instr);
311311
int _Py_Specialize_BinaryMultiply(PyObject *left, PyObject *right, _Py_CODEUNIT *instr);
312+
int _Py_Specialize_CallFunction(PyObject *callable, _Py_CODEUNIT *instr, int nargs, SpecializedCacheEntry *cache, PyObject *builtins);
312313

313314
#define PRINT_SPECIALIZATION_STATS 0
314315
#define PRINT_SPECIALIZATION_STATS_DETAILED 0

Include/opcode.h

Lines changed: 28 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/opcode.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ def jabs_op(name, op):
232232
"BINARY_SUBSCR_LIST_INT",
233233
"BINARY_SUBSCR_TUPLE_INT",
234234
"BINARY_SUBSCR_DICT",
235+
"CALL_FUNCTION_ADAPTIVE",
236+
"CALL_FUNCTION_BUILTIN_O",
237+
"CALL_FUNCTION_BUILTIN_FAST",
238+
"CALL_FUNCTION_LEN",
239+
"CALL_FUNCTION_ISINSTANCE",
235240
"JUMP_ABSOLUTE_QUICK",
236241
"LOAD_ATTR_ADAPTIVE",
237242
"LOAD_ATTR_INSTANCE_VALUE",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Setup initial specialization infrastructure for the ``CALL_FUNCTION`` opcode.
2+
Implemented initial specializations for C function calls:
3+
4+
* ``CALL_FUNCTION_BUILTIN_O`` for ``METH_O`` flag.
5+
6+
* ``CALL_FUNCTION_BUILTIN_FAST`` for ``METH_FASTCALL`` flag without keywords.
7+
8+
* ``CALL_FUNCTION_LEN`` for ``len(o)``.
9+
10+
* ``CALL_FUNCTION_ISINSTANCE`` for ``isinstance(o, t)``.

Python/ceval.c

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4660,6 +4660,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
46604660

46614661
TARGET(CALL_FUNCTION) {
46624662
PREDICTED(CALL_FUNCTION);
4663+
STAT_INC(CALL_FUNCTION, unquickened);
46634664
PyObject *function;
46644665
nargs = oparg;
46654666
kwnames = NULL;
@@ -4717,6 +4718,151 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
47174718
DISPATCH();
47184719
}
47194720

4721+
TARGET(CALL_FUNCTION_ADAPTIVE) {
4722+
SpecializedCacheEntry *cache = GET_CACHE();
4723+
if (cache->adaptive.counter == 0) {
4724+
next_instr--;
4725+
int nargs = cache->adaptive.original_oparg;
4726+
if (_Py_Specialize_CallFunction(
4727+
PEEK(nargs + 1), next_instr, nargs, cache, BUILTINS()) < 0) {
4728+
goto error;
4729+
}
4730+
DISPATCH();
4731+
}
4732+
else {
4733+
STAT_INC(CALL_FUNCTION, deferred);
4734+
cache->adaptive.counter--;
4735+
oparg = cache->adaptive.original_oparg;
4736+
JUMP_TO_INSTRUCTION(CALL_FUNCTION);
4737+
}
4738+
}
4739+
4740+
TARGET(CALL_FUNCTION_BUILTIN_O) {
4741+
assert(cframe.use_tracing == 0);
4742+
/* Builtin METH_O functions */
4743+
4744+
PyObject *callable = SECOND();
4745+
DEOPT_IF(!PyCFunction_CheckExact(callable), CALL_FUNCTION);
4746+
DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_O, CALL_FUNCTION);
4747+
_PyAdaptiveEntry *cache0 = &GET_CACHE()[0].adaptive;
4748+
record_cache_hit(cache0);
4749+
STAT_INC(CALL_FUNCTION, hit);
4750+
4751+
PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable);
4752+
PyObject *arg = POP();
4753+
PyObject *res = cfunc(PyCFunction_GET_SELF(callable), arg);
4754+
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
4755+
4756+
/* Clear the stack of the function object. */
4757+
Py_DECREF(arg);
4758+
Py_DECREF(callable);
4759+
SET_TOP(res);
4760+
if (res == NULL) {
4761+
goto error;
4762+
}
4763+
DISPATCH();
4764+
}
4765+
4766+
TARGET(CALL_FUNCTION_BUILTIN_FAST) {
4767+
assert(cframe.use_tracing == 0);
4768+
/* Builtin METH_FASTCALL functions, without keywords */
4769+
SpecializedCacheEntry *caches = GET_CACHE();
4770+
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
4771+
int nargs = cache0->original_oparg;
4772+
PyObject **pfunc = &PEEK(nargs + 1);
4773+
PyObject *callable = *pfunc;
4774+
DEOPT_IF(!PyCFunction_CheckExact(callable), CALL_FUNCTION);
4775+
DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_FASTCALL,
4776+
CALL_FUNCTION);
4777+
record_cache_hit(cache0);
4778+
STAT_INC(CALL_FUNCTION, hit);
4779+
4780+
PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable);
4781+
/* res = func(self, args, nargs) */
4782+
PyObject *res = ((_PyCFunctionFast)(void(*)(void))cfunc)(
4783+
PyCFunction_GET_SELF(callable),
4784+
&PEEK(nargs),
4785+
nargs);
4786+
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
4787+
4788+
/* Clear the stack of the function object. */
4789+
while (stack_pointer > pfunc) {
4790+
PyObject *x = POP();
4791+
Py_DECREF(x);
4792+
}
4793+
PUSH(res);
4794+
if (res == NULL) {
4795+
/* Not deopting because this doesn't mean our optimization was
4796+
wrong. `res` can be NULL for valid reasons. Eg. getattr(x,
4797+
'invalid'). In those cases an exception is set, so we must
4798+
handle it.
4799+
*/
4800+
goto error;
4801+
}
4802+
DISPATCH();
4803+
}
4804+
4805+
TARGET(CALL_FUNCTION_LEN) {
4806+
assert(cframe.use_tracing == 0);
4807+
/* len(o) */
4808+
SpecializedCacheEntry *caches = GET_CACHE();
4809+
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
4810+
_PyObjectCache *cache1 = &caches[-1].obj;
4811+
assert(cache0->original_oparg == 1);
4812+
4813+
PyObject *callable = SECOND();
4814+
DEOPT_IF(callable != cache1->obj, CALL_FUNCTION);
4815+
record_cache_hit(cache0);
4816+
STAT_INC(CALL_FUNCTION, hit);
4817+
4818+
Py_ssize_t len_i = PyObject_Length(TOP());
4819+
if (len_i < 0) {
4820+
goto error;
4821+
}
4822+
PyObject *res = PyLong_FromSsize_t(len_i);
4823+
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
4824+
4825+
/* Clear the stack of the function object. */
4826+
Py_DECREF(POP());
4827+
Py_DECREF(callable);
4828+
SET_TOP(res);
4829+
if (res == NULL) {
4830+
goto error;
4831+
}
4832+
DISPATCH();
4833+
}
4834+
4835+
TARGET(CALL_FUNCTION_ISINSTANCE) {
4836+
assert(cframe.use_tracing == 0);
4837+
/* isinstance(o, o2) */
4838+
SpecializedCacheEntry *caches = GET_CACHE();
4839+
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
4840+
_PyObjectCache *cache1 = &caches[-1].obj;
4841+
assert(cache0->original_oparg == 2);
4842+
4843+
PyObject *callable = THIRD();
4844+
DEOPT_IF(callable != cache1->obj, CALL_FUNCTION);
4845+
record_cache_hit(cache0);
4846+
STAT_INC(CALL_FUNCTION, hit);
4847+
4848+
int retval = PyObject_IsInstance(SECOND(), TOP());
4849+
if (retval < 0) {
4850+
goto error;
4851+
}
4852+
PyObject *res = PyBool_FromLong(retval);
4853+
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
4854+
4855+
/* Clear the stack of the function object. */
4856+
Py_DECREF(POP());
4857+
Py_DECREF(POP());
4858+
Py_DECREF(callable);
4859+
SET_TOP(res);
4860+
if (res == NULL) {
4861+
goto error;
4862+
}
4863+
DISPATCH();
4864+
}
4865+
47204866
TARGET(CALL_FUNCTION_EX) {
47214867
PREDICTED(CALL_FUNCTION_EX);
47224868
PyObject *func, *callargs, *kwargs = NULL, *result;
@@ -4985,6 +5131,7 @@ MISS_WITH_CACHE(LOAD_ATTR)
49855131
MISS_WITH_CACHE(STORE_ATTR)
49865132
MISS_WITH_CACHE(LOAD_GLOBAL)
49875133
MISS_WITH_CACHE(LOAD_METHOD)
5134+
MISS_WITH_CACHE(CALL_FUNCTION)
49885135
MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
49895136
MISS_WITH_OPARG_COUNTER(BINARY_ADD)
49905137
MISS_WITH_OPARG_COUNTER(BINARY_MULTIPLY)

Python/opcode_targets.h

Lines changed: 27 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)