Skip to content

Commit 616b745

Browse files
authored
GH-115709: Invalidate executors when a local variable is changed via frame.f_locals (#118639)
Also fix unrelated assert in debug Tier2/JIT builds.
1 parent 00d913c commit 616b745

File tree

5 files changed

+30
-4
lines changed

5 files changed

+30
-4
lines changed

Include/cpython/optimizer.h

+9-3
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,22 @@ void _Py_ExecutorDetach(_PyExecutorObject *);
141141
void _Py_BloomFilter_Init(_PyBloomFilter *);
142142
void _Py_BloomFilter_Add(_PyBloomFilter *bloom, void *obj);
143143
PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj);
144-
PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation);
145-
PyAPI_FUNC(void) _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation);
146-
147144
/* For testing */
148145
PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewCounter(void);
149146
PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewUOpOptimizer(void);
150147

151148
#define _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS 3
152149
#define _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS 6
153150

151+
#ifdef _Py_TIER2
152+
PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation);
153+
PyAPI_FUNC(void) _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation);
154+
#else
155+
# define _Py_Executors_InvalidateDependency(A, B, C) ((void)0)
156+
# define _Py_Executors_InvalidateAll(A, B) ((void)0)
157+
#endif
158+
159+
154160
#ifdef __cplusplus
155161
}
156162
#endif

Lib/test/test_capi/test_opt.py

+13
Original file line numberDiff line numberDiff line change
@@ -1321,5 +1321,18 @@ def testfunc(n):
13211321
self.assertIsNotNone(ex)
13221322
self.assertIn("_FOR_ITER_GEN_FRAME", get_opnames(ex))
13231323

1324+
def test_modified_local_is_seen_by_optimized_code(self):
1325+
l = sys._getframe().f_locals
1326+
a = 1
1327+
s = 0
1328+
for j in range(1 << 10):
1329+
a + a
1330+
l["xa"[j >> 9]] = 1.0
1331+
s += a
1332+
self.assertIs(type(a), float)
1333+
self.assertIs(type(s), float)
1334+
self.assertEqual(s, 1024.0)
1335+
1336+
13241337
if __name__ == "__main__":
13251338
unittest.main()

Objects/frameobject.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
148148
if (PyUnicode_CheckExact(key)) {
149149
int i = framelocalsproxy_getkeyindex(frame, key, false);
150150
if (i >= 0) {
151-
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
151+
_Py_Executors_InvalidateDependency(PyInterpreterState_Get(), co, 1);
152152

153+
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
153154
PyObject *oldvalue = fast[i];
154155
PyObject *cell = NULL;
155156
if (kind == CO_FAST_FREE) {

Python/bytecodes.c

+3
Original file line numberDiff line numberDiff line change
@@ -2424,6 +2424,9 @@ dummy_func(
24242424
opcode = executor->vm_data.opcode;
24252425
oparg = (oparg & ~255) | executor->vm_data.oparg;
24262426
next_instr = this_instr;
2427+
if (_PyOpcode_Caches[_PyOpcode_Deopt[opcode]]) {
2428+
PAUSE_ADAPTIVE_COUNTER(this_instr[1].counter);
2429+
}
24272430
DISPATCH_GOTO();
24282431
}
24292432
tstate->previous_executor = Py_None;

Python/generated_cases.c.h

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)