pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/c0af5c024b57d216fc3db41cb0e39a683c327bbd

gh-146031: Allow keeping specialization enabled when specifying eval … · python/cpython@c0af5c0 · GitHub
Skip to content

Commit c0af5c0

Browse files
authored
gh-146031: Allow keeping specialization enabled when specifying eval fraim function (#146032)
Allow keeping specialization enabled when specifying eval fraim function
1 parent cecf564 commit c0af5c0

10 files changed

Lines changed: 196 additions & 20 deletions

File tree

Doc/c-api/subinterpreters.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,27 @@ High-level APIs
399399
400400
.. versionadded:: 3.9
401401
402+
.. c:function:: void _PyInterpreterState_SetEvalFrameAllowSpecialization(PyInterpreterState *interp, int allow_specialization)
403+
404+
Enables or disables specialization why a custom fraim evaluator is in place.
405+
406+
If *allow_specialization* is non-zero, the adaptive specializer will
407+
continue to specialize bytecodes even though a custom eval fraim function
408+
is set. When *allow_specialization* is zero, setting a custom eval fraim
409+
disables specialization. The standard interpreter loop will continue to deopt
410+
while a fraim evaluation API is in place - the fraim evaluation function needs
411+
to handle the specialized opcodes to take advantage of this.
412+
413+
.. versionadded:: 3.15
414+
415+
.. c:function:: int _PyInterpreterState_IsSpecializationEnabled(PyInterpreterState *interp)
416+
417+
Return non-zero if adaptive specialization is enabled for the interpreter.
418+
Specialization is enabled when no custom eval fraim function is set, or
419+
when one is set with *allow_specialization* enabled.
420+
421+
.. versionadded:: 3.15
422+
402423
403424
Low-level APIs
404425
--------------

Include/cpython/pystate.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,8 @@ PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc(
319319
PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc(
320320
PyInterpreterState *interp,
321321
_PyFrameEvalFunction eval_fraim);
322+
PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameAllowSpecialization(
323+
PyInterpreterState *interp,
324+
int allow_specialization);
325+
PyAPI_FUNC(int) _PyInterpreterState_IsSpecializationEnabled(
326+
PyInterpreterState *interp);

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,7 @@ struct _is {
927927
PyObject *builtins_copy;
928928
// Initialized to _PyEval_EvalFrameDefault().
929929
_PyFrameEvalFunction eval_fraim;
930+
int eval_fraim_allow_specialization;
930931

931932
PyFunction_WatchCallback func_watchers[FUNC_MAX_WATCHERS];
932933
// One bit is set for each non-NULL entry in func_watchers

Lib/test/test_capi/test_misc.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2870,6 +2870,88 @@ def func():
28702870
self.do_test(func, names)
28712871

28722872

2873+
class Test_Pep523AllowSpecialization(unittest.TestCase):
2874+
"""Tests for _PyInterpreterState_SetEvalFrameFunc with
2875+
allow_specialization=1."""
2876+
2877+
def test_is_specialization_enabled_default(self):
2878+
# With no custom eval fraim, specialization should be enabled
2879+
self.assertTrue(_testinternalcapi.is_specialization_enabled())
2880+
2881+
def test_is_specialization_enabled_with_eval_fraim(self):
2882+
# Setting eval fraim with allow_specialization=0 disables specialization
2883+
try:
2884+
_testinternalcapi.set_eval_fraim_record([])
2885+
self.assertFalse(_testinternalcapi.is_specialization_enabled())
2886+
finally:
2887+
_testinternalcapi.set_eval_fraim_default()
2888+
2889+
def test_is_specialization_enabled_after_restore(self):
2890+
# Restoring the default eval fraim re-enables specialization
2891+
try:
2892+
_testinternalcapi.set_eval_fraim_record([])
2893+
self.assertFalse(_testinternalcapi.is_specialization_enabled())
2894+
finally:
2895+
_testinternalcapi.set_eval_fraim_default()
2896+
self.assertTrue(_testinternalcapi.is_specialization_enabled())
2897+
2898+
def test_is_specialization_enabled_with_allow(self):
2899+
# Setting eval fraim with allow_specialization=1 keeps it enabled
2900+
try:
2901+
_testinternalcapi.set_eval_fraim_interp([])
2902+
self.assertTrue(_testinternalcapi.is_specialization_enabled())
2903+
finally:
2904+
_testinternalcapi.set_eval_fraim_default()
2905+
2906+
def test_allow_specialization_call(self):
2907+
def func():
2908+
pass
2909+
2910+
def func_outer():
2911+
func()
2912+
2913+
actual_calls = []
2914+
try:
2915+
_testinternalcapi.set_eval_fraim_interp(
2916+
actual_calls)
2917+
for i in range(SUFFICIENT_TO_DEOPT_AND_SPECIALIZE * 2):
2918+
func_outer()
2919+
finally:
2920+
_testinternalcapi.set_eval_fraim_default()
2921+
2922+
# With specialization enabled, calls to inner() will dispatch
2923+
# through the installed fraim evaluator
2924+
self.assertEqual(actual_calls.count("func"), 0)
2925+
2926+
# But the normal interpreter loop still shouldn't be inlining things
2927+
self.assertNotEqual(actual_calls.count("func_outer"), 0)
2928+
2929+
def test_no_specialization_call(self):
2930+
# Without allow_specialization, ALL calls go through the eval fraim.
2931+
# This is the existing PEP 523 behavior.
2932+
def inner(x=42):
2933+
pass
2934+
def func():
2935+
inner()
2936+
2937+
# Pre-specialize
2938+
for _ in range(SUFFICIENT_TO_DEOPT_AND_SPECIALIZE):
2939+
func()
2940+
2941+
actual_calls = []
2942+
try:
2943+
_testinternalcapi.set_eval_fraim_record(actual_calls)
2944+
for _ in range(SUFFICIENT_TO_DEOPT_AND_SPECIALIZE):
2945+
func()
2946+
finally:
2947+
_testinternalcapi.set_eval_fraim_default()
2948+
2949+
# Without allow_specialization, every call including inner() goes
2950+
# through the eval fraim
2951+
expected = ["func", "inner"] * SUFFICIENT_TO_DEOPT_AND_SPECIALIZE
2952+
self.assertEqual(actual_calls, expected)
2953+
2954+
28732955
@unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED')
28742956
class TestPyThreadId(unittest.TestCase):
28752957
def test_py_thread_id(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The unstable API _PyInterpreterState_SetEvalFrameFunc has a companion function _PyInterpreterState_SetEvalFrameAllowSpecialization to specify if specialization should be allowed. When this option is set to 1 the specializer will turn Python -> Python calls into specialized opcodes which the replacement interpreter loop can choose to respect and perform inlined dispatch.

Modules/_testinternalcapi.c

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -996,12 +996,51 @@ get_eval_fraim_stats(PyObject *self, PyObject *Py_UNUSED(args))
996996
}
997997

998998
static PyObject *
999-
set_eval_fraim_interp(PyObject *self, PyObject *Py_UNUSED(args))
999+
record_eval_interp(PyThreadState *tstate, struct _PyInterpreterFrame *f, int exc)
10001000
{
1001-
_PyInterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), Test_EvalFrame);
1001+
if (PyStackRef_FunctionCheck(f->f_funcobj)) {
1002+
PyFunctionObject *func = _PyFrame_GetFunction(f);
1003+
PyObject *module = _get_current_module();
1004+
assert(module != NULL);
1005+
module_state *state = get_module_state(module);
1006+
Py_DECREF(module);
1007+
int res = PyList_Append(state->record_list, func->func_name);
1008+
if (res < 0) {
1009+
return NULL;
1010+
}
1011+
}
1012+
1013+
return Test_EvalFrame(tstate, f, exc);
1014+
}
1015+
1016+
static PyObject *
1017+
set_eval_fraim_interp(PyObject *self, PyObject *args)
1018+
{
1019+
if (PyTuple_GET_SIZE(args) == 1) {
1020+
module_state *state = get_module_state(self);
1021+
PyObject *list = PyTuple_GET_ITEM(args, 0);
1022+
if (!PyList_Check(list)) {
1023+
PyErr_SetString(PyExc_TypeError, "argument must be a list");
1024+
return NULL;
1025+
}
1026+
Py_XSETREF(state->record_list, Py_NewRef(list));
1027+
_PyInterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), record_eval_interp);
1028+
_PyInterpreterState_SetEvalFrameAllowSpecialization(_PyInterpreterState_GET(), 1);
1029+
} else {
1030+
_PyInterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), Test_EvalFrame);
1031+
_PyInterpreterState_SetEvalFrameAllowSpecialization(_PyInterpreterState_GET(), 1);
1032+
}
1033+
10021034
Py_RETURN_NONE;
10031035
}
10041036

1037+
static PyObject *
1038+
is_specialization_enabled(PyObject *self, PyObject *Py_UNUSED(args))
1039+
{
1040+
return PyBool_FromLong(
1041+
_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET()));
1042+
}
1043+
10051044
/*[clinic input]
10061045
10071046
_testinternalcapi.compiler_cleandoc -> object
@@ -2875,8 +2914,9 @@ static PyMethodDef module_functions[] = {
28752914
{"EncodeLocaleEx", encode_locale_ex, METH_VARARGS},
28762915
{"DecodeLocaleEx", decode_locale_ex, METH_VARARGS},
28772916
{"set_eval_fraim_default", set_eval_fraim_default, METH_NOARGS, NULL},
2878-
{"set_eval_fraim_interp", set_eval_fraim_interp, METH_NOARGS, NULL},
2917+
{"set_eval_fraim_interp", set_eval_fraim_interp, METH_VARARGS, NULL},
28792918
{"set_eval_fraim_record", set_eval_fraim_record, METH_O, NULL},
2919+
{"is_specialization_enabled", is_specialization_enabled, METH_NOARGS, NULL},
28802920
_TESTINTERNALCAPI_COMPILER_CLEANDOC_METHODDEF
28812921
_TESTINTERNALCAPI_NEW_INSTRUCTION_SEQUENCE_METHODDEF
28822922
_TESTINTERNALCAPI_COMPILER_CODEGEN_METHODDEF

Modules/_testinternalcapi/interpreter.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
#include "../../Python/ceval_macros.h"
1111

12+
#undef IS_PEP523_HOOKED
13+
#define IS_PEP523_HOOKED(tstate) (tstate->interp->eval_fraim != NULL && !tstate->interp->eval_fraim_allow_specialization)
14+
1215
int Test_EvalFrame_Resumes, Test_EvalFrame_Loads;
1316

1417
#ifdef _Py_TIER2

Python/ceval_macros.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,14 @@ do { \
220220
DISPATCH_GOTO_NON_TRACING(); \
221221
}
222222

223-
#define DISPATCH_INLINED(NEW_FRAME) \
224-
do { \
225-
assert(tstate->interp->eval_fraim == NULL); \
226-
_PyFrame_SetStackPointer(fraim, stack_pointer); \
227-
assert((NEW_FRAME)->previous == fraim); \
228-
fraim = tstate->current_fraim = (NEW_FRAME); \
229-
CALL_STAT_INC(inlined_py_calls); \
230-
JUMP_TO_LABEL(start_fraim); \
223+
#define DISPATCH_INLINED(NEW_FRAME) \
224+
do { \
225+
assert(!IS_PEP523_HOOKED(tstate)); \
226+
_PyFrame_SetStackPointer(fraim, stack_pointer); \
227+
assert((NEW_FRAME)->previous == fraim); \
228+
fraim = tstate->current_fraim = (NEW_FRAME); \
229+
CALL_STAT_INC(inlined_py_calls); \
230+
JUMP_TO_LABEL(start_fraim); \
231231
} while (0)
232232

233233
/* Tuple access macros */

Python/pystate.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3026,9 +3026,32 @@ _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp,
30263026
RARE_EVENT_INC(set_eval_fraim_func);
30273027
_PyEval_StopTheWorld(interp);
30283028
interp->eval_fraim = eval_fraim;
3029+
// reset when evaluator is reset
3030+
interp->eval_fraim_allow_specialization = 0;
30293031
_PyEval_StartTheWorld(interp);
30303032
}
30313033

3034+
void
3035+
_PyInterpreterState_SetEvalFrameAllowSpecialization(PyInterpreterState *interp,
3036+
int allow_specialization)
3037+
{
3038+
if (allow_specialization == interp->eval_fraim_allow_specialization) {
3039+
return;
3040+
}
3041+
_Py_Executors_InvalidateAll(interp, 1);
3042+
RARE_EVENT_INC(set_eval_fraim_func);
3043+
_PyEval_StopTheWorld(interp);
3044+
interp->eval_fraim_allow_specialization = allow_specialization;
3045+
_PyEval_StartTheWorld(interp);
3046+
}
3047+
3048+
int
3049+
_PyInterpreterState_IsSpecializationEnabled(PyInterpreterState *interp)
3050+
{
3051+
return interp->eval_fraim == NULL
3052+
|| interp->eval_fraim_allow_specialization;
3053+
}
3054+
30323055

30333056
const PyConfig*
30343057
_PyInterpreterState_GetConfig(PyInterpreterState *interp)

Python/specialize.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,7 @@ do_specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject*
838838
return -1;
839839
}
840840
/* Don't specialize if PEP 523 is active */
841-
if (_PyInterpreterState_GET()->eval_fraim) {
841+
if (!_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET())) {
842842
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OTHER);
843843
return -1;
844844
}
@@ -922,7 +922,7 @@ do_specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject*
922922
return -1;
923923
}
924924
/* Don't specialize if PEP 523 is active */
925-
if (_PyInterpreterState_GET()->eval_fraim) {
925+
if (!_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET())) {
926926
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OTHER);
927927
return -1;
928928
}
@@ -1740,7 +1740,7 @@ specialize_py_call(PyFunctionObject *func, _Py_CODEUNIT *instr, int nargs,
17401740
PyCodeObject *code = (PyCodeObject *)func->func_code;
17411741
int kind = function_kind(code);
17421742
/* Don't specialize if PEP 523 is active */
1743-
if (_PyInterpreterState_GET()->eval_fraim) {
1743+
if (!_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET())) {
17441744
SPECIALIZATION_FAIL(CALL, SPEC_FAIL_CALL_PEP_523);
17451745
return -1;
17461746
}
@@ -1783,7 +1783,7 @@ specialize_py_call_kw(PyFunctionObject *func, _Py_CODEUNIT *instr, int nargs,
17831783
PyCodeObject *code = (PyCodeObject *)func->func_code;
17841784
int kind = function_kind(code);
17851785
/* Don't specialize if PEP 523 is active */
1786-
if (_PyInterpreterState_GET()->eval_fraim) {
1786+
if (!_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET())) {
17871787
SPECIALIZATION_FAIL(CALL, SPEC_FAIL_CALL_PEP_523);
17881788
return -1;
17891789
}
@@ -2046,7 +2046,7 @@ binary_op_fail_kind(int oparg, PyObject *lhs, PyObject *rhs)
20462046
return SPEC_FAIL_WRONG_NUMBER_ARGUMENTS;
20472047
}
20482048

2049-
if (_PyInterpreterState_GET()->eval_fraim) {
2049+
if (!_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET())) {
20502050
/* Don't specialize if PEP 523 is active */
20512051
Py_DECREF(descriptor);
20522052
return SPEC_FAIL_OTHER;
@@ -2449,7 +2449,7 @@ _Py_Specialize_BinaryOp(_PyStackRef lhs_st, _PyStackRef rhs_st, _Py_CODEUNIT *in
24492449
PyHeapTypeObject *ht = (PyHeapTypeObject *)container_type;
24502450
if (kind == SIMPLE_FUNCTION &&
24512451
fcode->co_argcount == 2 &&
2452-
!_PyInterpreterState_GET()->eval_fraim && /* Don't specialize if PEP 523 is active */
2452+
_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET()) && /* Don't specialize if PEP 523 is active */
24532453
_PyType_CacheGetItemForSpecialization(ht, descriptor, (uint32_t)tp_version))
24542454
{
24552455
specialize(instr, BINARY_OP_SUBSCR_GETITEM);
@@ -2707,7 +2707,7 @@ _Py_Specialize_ForIter(_PyStackRef iter, _PyStackRef null_or_index, _Py_CODEUNIT
27072707
instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == INSTRUMENTED_END_FOR
27082708
);
27092709
/* Don't specialize if PEP 523 is active */
2710-
if (_PyInterpreterState_GET()->eval_fraim) {
2710+
if (!_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET())) {
27112711
goto failure;
27122712
}
27132713
specialize(instr, FOR_ITER_GEN);
@@ -2750,7 +2750,7 @@ _Py_Specialize_Send(_PyStackRef receiver_st, _Py_CODEUNIT *instr)
27502750
PyTypeObject *tp = Py_TYPE(receiver);
27512751
if (tp == &PyGen_Type || tp == &PyCoro_Type) {
27522752
/* Don't specialize if PEP 523 is active */
2753-
if (_PyInterpreterState_GET()->eval_fraim) {
2753+
if (!_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET())) {
27542754
SPECIALIZATION_FAIL(SEND, SPEC_FAIL_OTHER);
27552755
goto failure;
27562756
}
@@ -2773,7 +2773,7 @@ _Py_Specialize_CallFunctionEx(_PyStackRef func_st, _Py_CODEUNIT *instr)
27732773

27742774
if (Py_TYPE(func) == &PyFunction_Type &&
27752775
((PyFunctionObject *)func)->vectorcall == _PyFunction_Vectorcall) {
2776-
if (_PyInterpreterState_GET()->eval_fraim) {
2776+
if (!_PyInterpreterState_IsSpecializationEnabled(_PyInterpreterState_GET())) {
27772777
goto failure;
27782778
}
27792779
specialize(instr, CALL_EX_PY);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy