Skip to content

Commit

Permalink
GH-115709: Invalidate executors when a local variable is changed via …
Browse files Browse the repository at this point in the history
…frame.f_locals (#118639)

Also fix unrelated assert in debug Tier2/JIT builds.
  • Loading branch information
markshannon authored May 6, 2024
1 parent 00d913c commit 616b745
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 4 deletions.
12 changes: 9 additions & 3 deletions Include/cpython/optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,22 @@ void _Py_ExecutorDetach(_PyExecutorObject *);
void _Py_BloomFilter_Init(_PyBloomFilter *);
void _Py_BloomFilter_Add(_PyBloomFilter *bloom, void *obj);
PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj);
PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation);
PyAPI_FUNC(void) _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation);

/* For testing */
PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewCounter(void);
PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewUOpOptimizer(void);

#define _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS 3
#define _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS 6

#ifdef _Py_TIER2
PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation);
PyAPI_FUNC(void) _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation);
#else
# define _Py_Executors_InvalidateDependency(A, B, C) ((void)0)
# define _Py_Executors_InvalidateAll(A, B) ((void)0)
#endif


#ifdef __cplusplus
}
#endif
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1321,5 +1321,18 @@ def testfunc(n):
self.assertIsNotNone(ex)
self.assertIn("_FOR_ITER_GEN_FRAME", get_opnames(ex))

def test_modified_local_is_seen_by_optimized_code(self):
l = sys._getframe().f_locals
a = 1
s = 0
for j in range(1 << 10):
a + a
l["xa"[j >> 9]] = 1.0
s += a
self.assertIs(type(a), float)
self.assertIs(type(s), float)
self.assertEqual(s, 1024.0)


if __name__ == "__main__":
unittest.main()
3 changes: 2 additions & 1 deletion Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,9 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
if (PyUnicode_CheckExact(key)) {
int i = framelocalsproxy_getkeyindex(frame, key, false);
if (i >= 0) {
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
_Py_Executors_InvalidateDependency(PyInterpreterState_Get(), co, 1);

_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
PyObject *oldvalue = fast[i];
PyObject *cell = NULL;
if (kind == CO_FAST_FREE) {
Expand Down
3 changes: 3 additions & 0 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2424,6 +2424,9 @@ dummy_func(
opcode = executor->vm_data.opcode;
oparg = (oparg & ~255) | executor->vm_data.oparg;
next_instr = this_instr;
if (_PyOpcode_Caches[_PyOpcode_Deopt[opcode]]) {
PAUSE_ADAPTIVE_COUNTER(this_instr[1].counter);
}
DISPATCH_GOTO();
}
tstate->previous_executor = Py_None;
Expand Down
3 changes: 3 additions & 0 deletions Python/generated_cases.c.h

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

0 comments on commit 616b745

Please sign in to comment.