diff --git a/Include/cpython/code.h b/Include/cpython/code.h index d5dac1765638f9..8bfa5155c1a1a6 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -156,7 +156,7 @@ typedef struct { int co_ncellvars; /* total number of cell variables */ \ int co_nfreevars; /* number of free variables */ \ uint32_t co_version; /* version number */ \ - \ + int32_t co_expected_number_of_defaults; \ PyObject *co_localsplusnames; /* tuple mapping offsets to names */ \ PyObject *co_localspluskinds; /* Bytes mapping to local kinds (one byte \ per variable) */ \ @@ -170,6 +170,7 @@ typedef struct { uintptr_t _co_instrumentation_version; /* current instrumentation version */ \ _PyCoMonitoringData *_co_monitoring; /* Monitoring data */ \ int _co_firsttraceable; /* index of first traceable instruction */ \ + uint64_t co_expected_globals_version; \ /* Scratch space for extra data relating to the code object. \ Type is a void* to keep the format private in codeobject.c to force \ people to go through the proper APIs. */ \ diff --git a/Lib/test/test_optimizer.py b/Lib/test/test_optimizer.py index 899a4507317334..40220b85ba493f 100644 --- a/Lib/test/test_optimizer.py +++ b/Lib/test/test_optimizer.py @@ -1,6 +1,7 @@ import unittest import types from test.support import import_helper +from textwrap import dedent _testinternalcapi = import_helper.import_module("_testinternalcapi") @@ -78,6 +79,40 @@ def func(x=0): ) +class TestOptimizations(unittest.TestCase): + + def test_globals_to_consts(self): + #GH-117051 + src = """ + def f(): + global x + def inner(i): + a = 1 + for _ in range(100): + a = x + i + return a + return inner + + func = f() + """ + + co = compile(dedent(src), __file__, "exec") + + ns1 = {"x": 1000} + eval(co, ns1) + func1 = ns1["func"] + for i in range(1000): + x = func1(i) + self.assertEqual(x, 1999) + + ns2 = {"x": 2000} + eval(co, ns2) + func2 = ns2["func"] + for i in range(1000): + x = func2(i) + self.assertEqual(x, 2999) + + class TestOptimizerSymbols(unittest.TestCase): def test_optimizer_symbols(self): diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 3df733eb4ee578..120093a95579f2 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -414,11 +414,9 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con) co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE; co->co_ncellvars = ncellvars; co->co_nfreevars = nfreevars; - PyInterpreterState *interp = _PyInterpreterState_GET(); - co->co_version = interp->func_state.next_version; - if (interp->func_state.next_version != 0) { - interp->func_state.next_version++; - } + co->co_version = 0; + co->co_expected_globals_version = 0; + co->co_expected_number_of_defaults = -1; co->_co_monitoring = NULL; co->_co_instrumentation_version = 0; /* not set */ diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 476975d2fbc3c2..3cecbfec956a76 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3829,14 +3829,32 @@ dummy_func( PyFunctionObject *func_obj = (PyFunctionObject *) PyFunction_New(codeobj, GLOBALS()); - Py_DECREF(codeobj); if (func_obj == NULL) { GOTO_ERROR(error); } - - _PyFunction_SetVersion( - func_obj, ((PyCodeObject *)codeobj)->co_version); + PyCodeObject *code = (PyCodeObject *)codeobj; + if (func_obj->func_builtins != tstate->interp->builtins) { + _PyFunction_SetVersion(func_obj, 0); + } + else if (code->co_version == 0) { + code->co_version = tstate->interp->func_state.next_version; + if (tstate->interp->func_state.next_version != 0) { + tstate->interp->func_state.next_version++; + } + assert(code->co_expected_globals_version == 0); + if (PyDict_CheckExact(GLOBALS())) { + code->co_expected_globals_version = ((PyDictObject *)GLOBALS())->ma_version_tag; + } + _PyFunction_SetVersion(func_obj, code->co_version); + } + else { + if (PyDict_CheckExact(GLOBALS()) && + code->co_expected_globals_version == ((PyDictObject *)GLOBALS())->ma_version_tag) { + _PyFunction_SetVersion(func_obj, code->co_version); + } + } func = (PyObject *)func_obj; + Py_DECREF(codeobj); } inst(SET_FUNCTION_ATTRIBUTE, (attr, func -- func)) { @@ -3860,6 +3878,13 @@ dummy_func( assert(PyTuple_CheckExact(attr)); assert(func_obj->func_defaults == NULL); func_obj->func_defaults = attr; + PyCodeObject *code = (PyCodeObject *)func_obj->func_code; + if (code->co_expected_number_of_defaults < 0) { + code->co_expected_number_of_defaults = (int32_t)PyTuple_GET_SIZE(attr); + } + else if (code->co_expected_number_of_defaults != PyTuple_GET_SIZE(attr)) { + func_obj->func_version = 0; + } break; default: Py_UNREACHABLE(); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index a55daa2c344944..a2a64c4e0d66f5 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3506,13 +3506,32 @@ codeobj = stack_pointer[-1]; PyFunctionObject *func_obj = (PyFunctionObject *) PyFunction_New(codeobj, GLOBALS()); - Py_DECREF(codeobj); if (func_obj == NULL) { GOTO_ERROR(error); } - _PyFunction_SetVersion( - func_obj, ((PyCodeObject *)codeobj)->co_version); + PyCodeObject *code = (PyCodeObject *)codeobj; + if (func_obj->func_builtins != tstate->interp->builtins) { + _PyFunction_SetVersion(func_obj, 0); + } + else if (code->co_version == 0) { + code->co_version = tstate->interp->func_state.next_version; + if (tstate->interp->func_state.next_version != 0) { + tstate->interp->func_state.next_version++; + } + assert(code->co_expected_globals_version == 0); + if (PyDict_CheckExact(GLOBALS())) { + code->co_expected_globals_version = ((PyDictObject *)GLOBALS())->ma_version_tag; + } + _PyFunction_SetVersion(func_obj, code->co_version); + } + else { + if (PyDict_CheckExact(GLOBALS()) && + code->co_expected_globals_version == ((PyDictObject *)GLOBALS())->ma_version_tag) { + _PyFunction_SetVersion(func_obj, code->co_version); + } + } func = (PyObject *)func_obj; + Py_DECREF(codeobj); stack_pointer[-1] = func; break; } @@ -3543,6 +3562,13 @@ assert(PyTuple_CheckExact(attr)); assert(func_obj->func_defaults == NULL); func_obj->func_defaults = attr; + PyCodeObject *code = (PyCodeObject *)func_obj->func_code; + if (code->co_expected_number_of_defaults < 0) { + code->co_expected_number_of_defaults = (int32_t)PyTuple_GET_SIZE(attr); + } + else if (code->co_expected_number_of_defaults != PyTuple_GET_SIZE(attr)) { + func_obj->func_version = 0; + } break; default: Py_UNREACHABLE(); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 2996ee72e7f2c6..bb1a268633c22c 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -4589,13 +4589,32 @@ codeobj = stack_pointer[-1]; PyFunctionObject *func_obj = (PyFunctionObject *) PyFunction_New(codeobj, GLOBALS()); - Py_DECREF(codeobj); if (func_obj == NULL) { GOTO_ERROR(error); } - _PyFunction_SetVersion( - func_obj, ((PyCodeObject *)codeobj)->co_version); + PyCodeObject *code = (PyCodeObject *)codeobj; + if (func_obj->func_builtins != tstate->interp->builtins) { + _PyFunction_SetVersion(func_obj, 0); + } + else if (code->co_version == 0) { + code->co_version = tstate->interp->func_state.next_version; + if (tstate->interp->func_state.next_version != 0) { + tstate->interp->func_state.next_version++; + } + assert(code->co_expected_globals_version == 0); + if (PyDict_CheckExact(GLOBALS())) { + code->co_expected_globals_version = ((PyDictObject *)GLOBALS())->ma_version_tag; + } + _PyFunction_SetVersion(func_obj, code->co_version); + } + else { + if (PyDict_CheckExact(GLOBALS()) && + code->co_expected_globals_version == ((PyDictObject *)GLOBALS())->ma_version_tag) { + _PyFunction_SetVersion(func_obj, code->co_version); + } + } func = (PyObject *)func_obj; + Py_DECREF(codeobj); stack_pointer[-1] = func; DISPATCH(); } @@ -5231,6 +5250,13 @@ assert(PyTuple_CheckExact(attr)); assert(func_obj->func_defaults == NULL); func_obj->func_defaults = attr; + PyCodeObject *code = (PyCodeObject *)func_obj->func_code; + if (code->co_expected_number_of_defaults < 0) { + code->co_expected_number_of_defaults = (int32_t)PyTuple_GET_SIZE(attr); + } + else if (code->co_expected_number_of_defaults != PyTuple_GET_SIZE(attr)) { + func_obj->func_version = 0; + } break; default: Py_UNREACHABLE();