diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index bd9bc8a1a533c3..a12ee5700a1608 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -4288,9 +4288,35 @@ def g(): PYTHON_JIT="1", PYTHON_JIT_STRESS="1") self.assertEqual(result[0].rc, 0, result) + def test_func_version_watched_and_invalidated(self): + def testfunc(n): + for i in range(n): + # Only works on functions promoted to constants + global_identity_code_will_be_modified(i) + + testfunc(TIER2_THRESHOLD) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_PUSH_FRAME", uops) + # Both should be not present, as this is a call + # to a simple function with a known function version. + self.assertNotIn("_CHECK_FUNCTION_VERSION_INLINE", uops) + self.assertNotIn("_CHECK_FUNCTION_VERSION", uops) + + global_identity_code_will_be_modified.__code__ = (lambda a:a).__code__ + ex = get_first_executor(testfunc) + # Invalidated and removed. + self.assertIsNone(ex) + + def global_identity(x): return x +def global_identity_code_will_be_modified(x): + return x + class TestObject: def test(self, *args, **kwargs): return args[0] diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 585c7b9a85412c..d4858e8b4785e2 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -8,6 +8,7 @@ #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_object_deferred.h" // _PyObject_SetDeferredRefcount() +#include "pycore_optimizer.h" #include "pycore_pyerrors.h" // _PyErr_Occurred() #include "pycore_setobject.h" // _PySet_NextEntry() #include "pycore_stats.h" @@ -63,6 +64,9 @@ handle_func_event(PyFunction_WatchEvent event, PyFunctionObject *func, case PyFunction_EVENT_MODIFY_DEFAULTS: case PyFunction_EVENT_MODIFY_KWDEFAULTS: case PyFunction_EVENT_MODIFY_QUALNAME: +#if _Py_TIER2 + _Py_Executors_InvalidateDependency(interp, func, 1); +#endif RARE_EVENT_INTERP_INC(interp, func_modification); break; default: diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index f2d0e2940d7188..f6300fc0df1d97 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -864,6 +864,15 @@ dummy_func(void) { } op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { + PyObject *func = sym_get_probable_value(callable); + if (func == NULL || !PyFunction_Check(func)) { + ctx->contradiction = true; + ctx->done = true; + break; + } + // This could pass due to a global promoted const. + // So we need to add it to the dependencies on both branches. + _Py_BloomFilter_Add(dependencies, func); if (sym_get_func_version(callable) == func_version) { REPLACE_OP(this_instr, _NOP, 0, 0); } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 860bb02b7a0122..c92eecd0b1af41 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3039,6 +3039,13 @@ JitOptRef callable; callable = stack_pointer[-2 - oparg]; uint32_t func_version = (uint32_t)this_instr->operand0; + PyObject *func = sym_get_probable_value(callable); + if (func == NULL || !PyFunction_Check(func)) { + ctx->contradiction = true; + ctx->done = true; + break; + } + _Py_BloomFilter_Add(dependencies, func); if (sym_get_func_version(callable) == func_version) { REPLACE_OP(this_instr, _NOP, 0, 0); }