Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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_global_objects_fini_generated.h

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

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

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

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

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

17 changes: 16 additions & 1 deletion Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,22 @@ def test_exec_filter_syntax_warnings_by_module(self):
self.assertEqual(wm.filename, '<string>')
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])
Expand Down
21 changes: 16 additions & 5 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.

Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down
51 changes: 51 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down
60 changes: 43 additions & 17 deletions Python/clinic/bltinmodule.c.h

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

Loading