diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index e625bf2fef1912..89fa44212a363c 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -2080,6 +2080,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sub_key)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(subcalls)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(symmetric_difference_update)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sync_fast_locals)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tabsize)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tag)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(take_bytes)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 771f0f8cb4ad87..7637fc81b07f75 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -803,6 +803,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(sub_key) STRUCT_FOR_ID(subcalls) STRUCT_FOR_ID(symmetric_difference_update) + STRUCT_FOR_ID(sync_fast_locals) STRUCT_FOR_ID(tabsize) STRUCT_FOR_ID(tag) STRUCT_FOR_ID(take_bytes) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 499a2569b9a06c..05cf6434971f39 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -2078,6 +2078,7 @@ extern "C" { INIT_ID(sub_key), \ INIT_ID(subcalls), \ INIT_ID(symmetric_difference_update), \ + INIT_ID(sync_fast_locals), \ INIT_ID(tabsize), \ INIT_ID(tag), \ INIT_ID(take_bytes), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 1375f46018f943..dd0fa579bc4be2 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -2992,6 +2992,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(sync_fast_locals); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(tabsize); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index ce60a5d095dd52..1bdc7ca62510a6 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1111,7 +1111,22 @@ def test_exec_filter_syntax_warnings_by_module(self): self.assertEqual(wm.filename, '') self.assertIs(wm.category, SyntaxWarning) - + def test_eval_exec_sync_fast_locals(self): + def func_assign(): + a = 1 + + def func_read(): + b = a + 1 + + for executor in eval, exec: + with self.subTest(executor=executor.__name__): + ns = {} + executor(func_assign.__code__, {}, ns, sync_fast_locals=True) + self.assertEqual(ns, {'a': 1}) + ns = {'a': 1} + executor(func_read.__code__, {}, ns, sync_fast_locals=True) + self.assertEqual(ns, {'a': 1, 'b': 2}) + def test_filter(self): self.assertEqual(list(filter(lambda c: 'a' <= c <= 'z', 'Hello World')), list('elloorld')) self.assertEqual(list(filter(None, [1, 'hello', [], [3], '', None, 9, 0])), [1, 'hello', [3], 9]) diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index c2d780ac9b9270..f383d0bbf84966 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -955,6 +955,8 @@ eval as builtin_eval / globals: object = None locals: object = None + * + sync_fast_locals: bool = False Evaluate the given source in the context of globals and locals. @@ -967,8 +969,8 @@ If only globals is given, locals defaults to it. static PyObject * builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, - PyObject *locals) -/*[clinic end generated code: output=0a0824aa70093116 input=7c7bce5299a89062]*/ + PyObject *locals, int sync_fast_locals) +/*[clinic end generated code: output=a573401639e51347 input=440105eb08930503]*/ { PyThreadState *tstate = _PyThreadState_GET(); PyObject *result = NULL, *source_copy; @@ -1037,6 +1039,10 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, "code object passed to eval() may not contain free variables"); goto error; } + if (!sync_fast_locals && ((PyCodeObject *)source)->co_flags & CO_OPTIMIZED) { + Py_DECREF(locals); + locals = globals; + } result = PyEval_EvalCode(source, globals, locals); } else { @@ -1078,6 +1084,7 @@ exec as builtin_exec locals: object = None * closure: object(c_default="NULL") = None + sync_fast_locals: bool = False Execute the given source in the context of globals and locals. @@ -1092,8 +1099,8 @@ when source is a code object requiring exactly that many cellvars. static PyObject * builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, - PyObject *locals, PyObject *closure) -/*[clinic end generated code: output=7579eb4e7646743d input=25e989b6d87a3a21]*/ + PyObject *locals, PyObject *closure, int sync_fast_locals) +/*[clinic end generated code: output=ceab303bd7575dcf input=3a4103a242b26356]*/ { PyThreadState *tstate = _PyThreadState_GET(); PyObject *v; @@ -1189,6 +1196,10 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, goto error; } + if (!sync_fast_locals && ((PyCodeObject *)source)->co_flags & CO_OPTIMIZED) { + Py_DECREF(locals); + locals = NULL; + } if (!closure) { v = PyEval_EvalCode(source, globals, locals); } else { @@ -1225,7 +1236,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, if (v == NULL) goto error; Py_DECREF(globals); - Py_DECREF(locals); + Py_XDECREF(locals); Py_DECREF(v); Py_RETURN_NONE; diff --git a/Python/ceval.c b/Python/ceval.c index 924afaa97443cb..16df5e39567eaa 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1529,6 +1529,46 @@ typedef struct { _PyStackRef stack[1]; } _PyEntryFrame; +static int +_PyEval_SyncLocalsToFast(_PyInterpreterFrame *frame) +{ + PyObject *mapping = frame->f_locals; + PyCodeObject *co = _PyFrame_GetCode(frame); + PyObject *names = co->co_localsplusnames; + + for (int i = 0; i < co->co_nlocalsplus; i++) { + PyObject *name = PyTuple_GET_ITEM(names, i); + PyObject *value = PyObject_GetItem(mapping, name); + if (value != NULL) { + frame->localsplus[i] = PyStackRef_FromPyObjectSteal(value); + } + else { + return -1; + } + } + return 0; +} + +static int +_PyEval_SyncFastToLocals(_PyInterpreterFrame *frame) +{ + PyObject *mapping = frame->f_locals; + PyCodeObject *co = _PyFrame_GetCode(frame); + PyObject *names = co->co_localsplusnames; + + for (int i = 0; i < co->co_nlocalsplus; i++) { + _PyStackRef sref = frame->localsplus[i]; + if (!PyStackRef_IsNull(sref)) { + PyObject *name = PyTuple_GET_ITEM(names, i); + PyObject *obj = PyStackRef_AsPyObjectSteal(sref); + if (PyObject_SetItem(mapping, name, obj) < 0) { + return -1; + } + } + } + return 0; +} + PyObject* _Py_HOT_FUNCTION DONT_SLP_VECTORIZE _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag) { @@ -1591,6 +1631,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int frame->previous = &entry.frame; tstate->current_frame = frame; entry.frame.localsplus[0] = PyStackRef_NULL; + PyCodeObject *co = _PyFrame_GetCode(frame); + if ((co->co_flags & CO_OPTIMIZED) && frame->f_locals != NULL && + frame->f_locals != frame->f_globals && _PyEval_SyncLocalsToFast(frame) < 0) { + goto early_exit; + } #ifdef _Py_TIER2 if (tstate->current_executor != NULL) { entry.frame.localsplus[0] = PyStackRef_FromPyObjectNew(tstate->current_executor); @@ -2377,6 +2422,12 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame) void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame * frame) { + PyCodeObject *co = _PyFrame_GetCode(frame); + if ((co->co_flags & CO_OPTIMIZED) && frame->f_locals != NULL && + frame->f_locals != frame->f_globals && _PyEval_SyncFastToLocals(frame) < 0) { + /* Swallow the error while the frame is in a teardown state */ + PyErr_WriteUnraisable(frame->f_locals); + } // Update last_profiled_frame for remote profiler frame caching. // By this point, tstate->current_frame is already set to the parent frame. // Only update if we're popping the exact frame that was last profiled. diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index f08e5847abe32a..7b9e438a2fe822 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -412,7 +412,8 @@ builtin_divmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(builtin_eval__doc__, -"eval($module, source, /, globals=None, locals=None)\n" +"eval($module, source, /, globals=None, locals=None, *,\n" +" sync_fast_locals=False)\n" "--\n" "\n" "Evaluate the given source in the context of globals and locals.\n" @@ -428,7 +429,7 @@ PyDoc_STRVAR(builtin_eval__doc__, static PyObject * builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, - PyObject *locals); + PyObject *locals, int sync_fast_locals); static PyObject * builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -436,7 +437,7 @@ builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 2 + #define NUM_KEYWORDS 3 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -445,7 +446,7 @@ builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(globals), &_Py_ID(locals), }, + .ob_item = { &_Py_ID(globals), &_Py_ID(locals), &_Py_ID(sync_fast_locals), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -454,18 +455,19 @@ builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "globals", "locals", NULL}; + static const char * const _keywords[] = {"", "globals", "locals", "sync_fast_locals", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "eval", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[3]; + PyObject *argsbuf[4]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; PyObject *source; PyObject *globals = Py_None; PyObject *locals = Py_None; + int sync_fast_locals = 0; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -482,16 +484,30 @@ builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject goto skip_optional_pos; } } - locals = args[2]; + if (args[2]) { + locals = args[2]; + if (!--noptargs) { + goto skip_optional_pos; + } + } skip_optional_pos: - return_value = builtin_eval_impl(module, source, globals, locals); + if (!noptargs) { + goto skip_optional_kwonly; + } + sync_fast_locals = PyObject_IsTrue(args[3]); + if (sync_fast_locals < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = builtin_eval_impl(module, source, globals, locals, sync_fast_locals); exit: return return_value; } PyDoc_STRVAR(builtin_exec__doc__, -"exec($module, source, /, globals=None, locals=None, *, closure=None)\n" +"exec($module, source, /, globals=None, locals=None, *, closure=None,\n" +" sync_fast_locals=False)\n" "--\n" "\n" "Execute the given source in the context of globals and locals.\n" @@ -509,7 +525,7 @@ PyDoc_STRVAR(builtin_exec__doc__, static PyObject * builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, - PyObject *locals, PyObject *closure); + PyObject *locals, PyObject *closure, int sync_fast_locals); static PyObject * builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -517,7 +533,7 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 3 + #define NUM_KEYWORDS 4 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -526,7 +542,7 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(globals), &_Py_ID(locals), &_Py_ID(closure), }, + .ob_item = { &_Py_ID(globals), &_Py_ID(locals), &_Py_ID(closure), &_Py_ID(sync_fast_locals), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -535,19 +551,20 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "globals", "locals", "closure", NULL}; + static const char * const _keywords[] = {"", "globals", "locals", "closure", "sync_fast_locals", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "exec", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[4]; + PyObject *argsbuf[5]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; PyObject *source; PyObject *globals = Py_None; PyObject *locals = Py_None; PyObject *closure = NULL; + int sync_fast_locals = 0; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -574,9 +591,18 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject if (!noptargs) { goto skip_optional_kwonly; } - closure = args[3]; + if (args[3]) { + closure = args[3]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + sync_fast_locals = PyObject_IsTrue(args[4]); + if (sync_fast_locals < 0) { + goto exit; + } skip_optional_kwonly: - return_value = builtin_exec_impl(module, source, globals, locals, closure); + return_value = builtin_exec_impl(module, source, globals, locals, closure, sync_fast_locals); exit: return return_value; @@ -1285,4 +1311,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=06500bcc9a341e68 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3870be11257135c3 input=a9049054013a1b77]*/