From 2e43f75c0975aa3b7f66f0a42762888f795808eb Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 May 2024 11:51:52 +0100 Subject: [PATCH 1/5] Invlidate executors when a local variable in changed via frame.f_locals --- Lib/test/test_capi/test_opt.py | 13 +++++++++++++ Objects/frameobject.c | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 6e5b626e93291a..0491ff9b84d486 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -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() diff --git a/Objects/frameobject.c b/Objects/frameobject.c index aac809b459a430..5bcc32d19c0f58 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -149,7 +149,7 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value) 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); PyObject *oldvalue = fast[i]; PyObject *cell = NULL; if (kind == CO_FAST_FREE) { From 7cff2b7b5b3995b4f0d6e018fb0a409da69d9121 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 May 2024 12:03:04 +0100 Subject: [PATCH 2/5] Support (no-op) invalidation in tier 1. --- Python/optimizer.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 8be2c0ffbd78e9..2956b95d9cd7f0 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1,6 +1,7 @@ +#include "Python.h" + #ifdef _Py_TIER2 -#include "Python.h" #include "opcode.h" #include "pycore_interp.h" #include "pycore_backoff.h" @@ -1721,4 +1722,13 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation) } } +#else +void +_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation) +{ + (void)interp; + (void)obj; + (void)is_invalidation; +} + #endif /* _Py_TIER2 */ From d7bbf67a38c066ca5ea6455e1452013503f6b4c5 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 May 2024 12:05:13 +0100 Subject: [PATCH 3/5] Tidy up --- Objects/frameobject.c | 3 ++- Python/optimizer.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 5bcc32d19c0f58..82d1706efd41b7 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -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) { diff --git a/Python/optimizer.c b/Python/optimizer.c index 2956b95d9cd7f0..b3d36ae81c4447 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1723,6 +1723,7 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation) } #else + void _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation) { From 83094c1097050d2dace759a1ccd3c64bac4cbdbd Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 May 2024 13:59:54 +0100 Subject: [PATCH 4/5] Prevent re-specialization of ENTER_EXECUTOR. --- Python/bytecodes.c | 3 +++ Python/generated_cases.c.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index b2a0dc030e20cc..b2ddec98e682f2 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -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; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 87098b0506522f..d3126b0c980723 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2673,6 +2673,9 @@ 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; From c7125c9a62bb3b21ac0576b0fee4aadcb1e693b8 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 May 2024 19:25:39 +0100 Subject: [PATCH 5/5] #define away _Py_Executors_InvalidateDependency in non-tier2 build. --- Include/cpython/optimizer.h | 12 +++++++++--- Python/optimizer.c | 13 +------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index 744a272251e75c..5f218d75b346a0 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -141,9 +141,6 @@ 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); @@ -151,6 +148,15 @@ 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 diff --git a/Python/optimizer.c b/Python/optimizer.c index b3d36ae81c4447..8be2c0ffbd78e9 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1,7 +1,6 @@ -#include "Python.h" - #ifdef _Py_TIER2 +#include "Python.h" #include "opcode.h" #include "pycore_interp.h" #include "pycore_backoff.h" @@ -1722,14 +1721,4 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation) } } -#else - -void -_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation) -{ - (void)interp; - (void)obj; - (void)is_invalidation; -} - #endif /* _Py_TIER2 */