Skip to content

bpo-44525: Specialize CALL_FUNCTION for C function calls #26934

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 55 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
5e73b74
WIP: Specialize CALL_FUNCTION for builtins
Fidget-Spinner Jun 26, 2021
1539105
fix some GCC compilation warnings
Fidget-Spinner Jun 26, 2021
68e5451
hopefully fix the segfaults
Fidget-Spinner Jun 26, 2021
1d841b0
Rename to CALL_CFUNCTION and generalize to all c functions
Fidget-Spinner Jun 27, 2021
f41b623
fix formatting, remove redundant check
Fidget-Spinner Jun 27, 2021
de520bd
goto fail rather than return -1
Fidget-Spinner Jun 28, 2021
0e0a3a4
Create 2021-06-28-22-23-59.bpo-44525.sSvUKG.rst
Fidget-Spinner Jun 28, 2021
65de42d
Apply easier suggestions from Mark's review
Fidget-Spinner Jun 29, 2021
685557f
Only specialize METH_FASTCALL and METH_O
Fidget-Spinner Jun 30, 2021
5baa936
Update 2021-06-28-22-23-59.bpo-44525.sSvUKG.rst
Fidget-Spinner Jun 30, 2021
bc69360
turn off specialization stats flag
Fidget-Spinner Jun 30, 2021
8671a60
Apply suggestions by Mark
Fidget-Spinner Jul 3, 2021
a8b8b4f
reduce diff
Fidget-Spinner Jul 3, 2021
736d9af
use PyMapping_HasKeyString since PyDict_GetItemString is discouraged
Fidget-Spinner Jul 3, 2021
2e3195d
fix reference leak
Fidget-Spinner Jul 3, 2021
d8b3a09
remove unused variable, add more specialization fails
Fidget-Spinner Jul 3, 2021
25b002c
don't allow specialized function calls when tracing
Fidget-Spinner Jul 3, 2021
557e4bc
deopt when tracing
Fidget-Spinner Jul 3, 2021
e554d64
Merge remote-tracking branch 'upstream/main' into call_function_speci…
Fidget-Spinner Jul 8, 2021
ea0d432
apply mark's comments
Fidget-Spinner Jul 8, 2021
97749b7
change deopts to asserts
Fidget-Spinner Jul 8, 2021
feb966a
add blank lines between each case
Fidget-Spinner Jul 13, 2021
9a5a407
Remove CALL_CFUNCTION_FAST
Fidget-Spinner Jul 13, 2021
4dafd8d
Apply Mark's suggestions
Fidget-Spinner Jul 14, 2021
84d2367
delete useless comment, add back useful one
Fidget-Spinner Jul 14, 2021
12a5333
remove complicated checks for classes
Fidget-Spinner Jul 14, 2021
b99f65c
apply suggestions by Mark
Fidget-Spinner Jul 15, 2021
5239342
actually move it into the block this time
Fidget-Spinner Jul 15, 2021
c4d6ca3
Merge remote-tracking branch 'upstream/main' into call_function_speci…
Fidget-Spinner Jul 15, 2021
7250329
Regen opcodes
Fidget-Spinner Jul 15, 2021
c649070
move type earlier
Fidget-Spinner Jul 15, 2021
5dfce16
change to assert
Fidget-Spinner Jul 16, 2021
0da5ed2
increment unquickened stats
Fidget-Spinner Jul 16, 2021
40b919f
Re-add CALL_FUNCTION_BUILTIN_FAST
Fidget-Spinner Jul 16, 2021
75e3540
add check for C methods
Fidget-Spinner Jul 16, 2021
3e2766c
Merge remote-tracking branch 'upstream/main' into call_function_speci…
Fidget-Spinner Jul 27, 2021
c06f5b8
Merge remote-tracking branch 'upstream/main' into call_function_speci…
Fidget-Spinner Sep 15, 2021
3c1129a
fix build errors
Fidget-Spinner Sep 15, 2021
226c591
Add CALL_FUNCTION_LEN
Fidget-Spinner Sep 17, 2021
2dc2738
add CALL_FUNCTION_ISINSTANCE
Fidget-Spinner Sep 17, 2021
7bd4338
fix specialization stats
Fidget-Spinner Sep 17, 2021
e2aada7
Merge remote-tracking branch 'upstream/main' into call_function_speci…
Fidget-Spinner Sep 17, 2021
f8c0957
Merge remote-tracking branch 'upstream/main' into call_function_speci…
Fidget-Spinner Oct 18, 2021
2500ab6
Refactor
Fidget-Spinner Oct 18, 2021
08ef4d8
convert to static
Fidget-Spinner Oct 18, 2021
41f6fa6
fix news and formatting
Fidget-Spinner Oct 18, 2021
8b113d1
remove typo
Fidget-Spinner Oct 18, 2021
9642df5
remove nit
Fidget-Spinner Oct 18, 2021
8a74cff
fix wrong return code
Fidget-Spinner Oct 18, 2021
907c5cb
partly address code review
Fidget-Spinner Oct 18, 2021
3e09485
Exclude function if not collecting stats
Fidget-Spinner Oct 19, 2021
b28d85c
check for error first
Fidget-Spinner Oct 19, 2021
617424b
Record cache hit earlier
Fidget-Spinner Oct 19, 2021
e73b69f
fix isinstance bug
Fidget-Spinner Oct 19, 2021
f191720
apply suggestions from review: move up cache hits
Fidget-Spinner Oct 19, 2021
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
1 change: 1 addition & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ int _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *na
int _Py_Specialize_BinarySubscr(PyObject *sub, PyObject *container, _Py_CODEUNIT *instr);
int _Py_Specialize_BinaryAdd(PyObject *left, PyObject *right, _Py_CODEUNIT *instr);
int _Py_Specialize_BinaryMultiply(PyObject *left, PyObject *right, _Py_CODEUNIT *instr);
int _Py_Specialize_CallFunction(PyObject *callable, _Py_CODEUNIT *instr, int nargs, SpecializedCacheEntry *cache, PyObject *builtins);

#define PRINT_SPECIALIZATION_STATS 0
#define PRINT_SPECIALIZATION_STATS_DETAILED 0
Expand Down
51 changes: 28 additions & 23 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ def jabs_op(name, op):
"BINARY_SUBSCR_LIST_INT",
"BINARY_SUBSCR_TUPLE_INT",
"BINARY_SUBSCR_DICT",
"CALL_FUNCTION_ADAPTIVE",
"CALL_FUNCTION_BUILTIN_O",
"CALL_FUNCTION_BUILTIN_FAST",
"CALL_FUNCTION_LEN",
"CALL_FUNCTION_ISINSTANCE",
"JUMP_ABSOLUTE_QUICK",
"LOAD_ATTR_ADAPTIVE",
"LOAD_ATTR_INSTANCE_VALUE",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Setup initial specialization infrastructure for the ``CALL_FUNCTION`` opcode.
Implemented initial specializations for C function calls:

* ``CALL_FUNCTION_BUILTIN_O`` for ``METH_O`` flag.

* ``CALL_FUNCTION_BUILTIN_FAST`` for ``METH_FASTCALL`` flag without keywords.

* ``CALL_FUNCTION_LEN`` for ``len(o)``.

* ``CALL_FUNCTION_ISINSTANCE`` for ``isinstance(o, t)``.
147 changes: 147 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -4657,6 +4657,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr

TARGET(CALL_FUNCTION) {
PREDICTED(CALL_FUNCTION);
STAT_INC(CALL_FUNCTION, unquickened);
PyObject *function;
nargs = oparg;
kwnames = NULL;
Expand Down Expand Up @@ -4714,6 +4715,151 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
DISPATCH();
}

TARGET(CALL_FUNCTION_ADAPTIVE) {
SpecializedCacheEntry *cache = GET_CACHE();
if (cache->adaptive.counter == 0) {
next_instr--;
int nargs = cache->adaptive.original_oparg;
if (_Py_Specialize_CallFunction(
PEEK(nargs + 1), next_instr, nargs, cache, BUILTINS()) < 0) {
goto error;
}
DISPATCH();
}
else {
STAT_INC(CALL_FUNCTION, deferred);
cache->adaptive.counter--;
oparg = cache->adaptive.original_oparg;
JUMP_TO_INSTRUCTION(CALL_FUNCTION);
}
}

TARGET(CALL_FUNCTION_BUILTIN_O) {
assert(cframe.use_tracing == 0);
/* Builtin METH_O functions */

PyObject *callable = SECOND();
DEOPT_IF(!PyCFunction_CheckExact(callable), CALL_FUNCTION);
DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_O, CALL_FUNCTION);
_PyAdaptiveEntry *cache0 = &GET_CACHE()[0].adaptive;
record_cache_hit(cache0);
STAT_INC(CALL_FUNCTION, hit);

PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable);
PyObject *arg = POP();
PyObject *res = cfunc(PyCFunction_GET_SELF(callable), arg);
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));

/* Clear the stack of the function object. */
Py_DECREF(arg);
Py_DECREF(callable);
SET_TOP(res);
if (res == NULL) {
goto error;
}
DISPATCH();
}

TARGET(CALL_FUNCTION_BUILTIN_FAST) {
assert(cframe.use_tracing == 0);
/* Builtin METH_FASTCALL functions, without keywords */
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
int nargs = cache0->original_oparg;
PyObject **pfunc = &PEEK(nargs + 1);
PyObject *callable = *pfunc;
DEOPT_IF(!PyCFunction_CheckExact(callable), CALL_FUNCTION);
DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_FASTCALL,
CALL_FUNCTION);
record_cache_hit(cache0);
STAT_INC(CALL_FUNCTION, hit);

PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable);
/* res = func(self, args, nargs) */
PyObject *res = ((_PyCFunctionFast)(void(*)(void))cfunc)(
PyCFunction_GET_SELF(callable),
&PEEK(nargs),
nargs);
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));

/* Clear the stack of the function object. */
while (stack_pointer > pfunc) {
PyObject *x = POP();
Py_DECREF(x);
}
PUSH(res);
if (res == NULL) {
/* Not deopting because this doesn't mean our optimization was
wrong. `res` can be NULL for valid reasons. Eg. getattr(x,
'invalid'). In those cases an exception is set, so we must
handle it.
*/
goto error;
}
DISPATCH();
}

TARGET(CALL_FUNCTION_LEN) {
assert(cframe.use_tracing == 0);
/* len(o) */
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyObjectCache *cache1 = &caches[-1].obj;
assert(cache0->original_oparg == 1);

PyObject *callable = SECOND();
DEOPT_IF(callable != cache1->obj, CALL_FUNCTION);
record_cache_hit(cache0);
STAT_INC(CALL_FUNCTION, hit);

Py_ssize_t len_i = PyObject_Length(TOP());
if (len_i < 0) {
goto error;
}
PyObject *res = PyLong_FromSsize_t(len_i);
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));

/* Clear the stack of the function object. */
Py_DECREF(POP());
Py_DECREF(callable);
SET_TOP(res);
if (res == NULL) {
goto error;
}
DISPATCH();
}

TARGET(CALL_FUNCTION_ISINSTANCE) {
assert(cframe.use_tracing == 0);
/* isinstance(o, o2) */
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyObjectCache *cache1 = &caches[-1].obj;
assert(cache0->original_oparg == 2);

PyObject *callable = THIRD();
DEOPT_IF(callable != cache1->obj, CALL_FUNCTION);
record_cache_hit(cache0);
STAT_INC(CALL_FUNCTION, hit);

int retval = PyObject_IsInstance(SECOND(), TOP());
if (retval < 0) {
goto error;
}
PyObject *res = PyBool_FromLong(retval);
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));

/* Clear the stack of the function object. */
Py_DECREF(POP());
Py_DECREF(POP());
Py_DECREF(callable);
SET_TOP(res);
if (res == NULL) {
goto error;
}
DISPATCH();
}

TARGET(CALL_FUNCTION_EX) {
PREDICTED(CALL_FUNCTION_EX);
PyObject *func, *callargs, *kwargs = NULL, *result;
Expand Down Expand Up @@ -4982,6 +5128,7 @@ MISS_WITH_CACHE(LOAD_ATTR)
MISS_WITH_CACHE(STORE_ATTR)
MISS_WITH_CACHE(LOAD_GLOBAL)
MISS_WITH_CACHE(LOAD_METHOD)
MISS_WITH_CACHE(CALL_FUNCTION)
MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
MISS_WITH_OPARG_COUNTER(BINARY_ADD)
MISS_WITH_OPARG_COUNTER(BINARY_MULTIPLY)
Expand Down
54 changes: 27 additions & 27 deletions Python/opcode_targets.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading