From abb97dc511ec7d897c86f6ede119ec2e0445dda0 Mon Sep 17 00:00:00 2001 From: Matt Page Date: Mon, 5 Aug 2024 11:01:15 -0700 Subject: [PATCH 1/2] Add repro --- Lib/test/test_opcache.py | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 92a34113bc0383..c4fcc1993ca627 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -28,6 +28,13 @@ def wrapper(*args, **kwargs): return wrapper +class TestBase(unittest.TestCase): + def assert_specialized(self, f, opname): + instructions = dis.get_instructions(f, adaptive=True) + opnames = {instruction.opname for instruction in instructions} + self.assertIn(opname, opnames) + + class TestLoadSuperAttrCache(unittest.TestCase): def test_descriptor_not_double_executed_on_spec_fail(self): calls = [] @@ -479,7 +486,7 @@ def f(): self.assertFalse(f()) -class TestCallCache(unittest.TestCase): +class TestCallCache(TestBase): def test_too_many_defaults_0(self): def f(): pass @@ -507,10 +514,33 @@ def f(x, y): f(None) f() + @disabling_optimizer + @requires_specialization + def test_assign_init_code(self): + class MyClass: + def __init__(self): + pass + + def instantiate(): + return MyClass() + + # Trigger specialization + for _ in range(1025): + instantiate() + self.assert_specialized(instantiate, "CALL_ALLOC_AND_ENTER_INIT") + + def count_args(self, *args): + self.num_args = len(args) + + # Set MyClass.__init__.__code__ to a code object that is incompatible + # (uses varargs) with the current specialization + MyClass.__init__.__code__ = count_args.__code__ + instantiate() + @threading_helper.requires_working_threading() @requires_specialization -class TestRacesDoNotCrash(unittest.TestCase): +class TestRacesDoNotCrash(TestBase): # Careful with these. Bigger numbers have a higher chance of catching bugs, # but you can also burn through a *ton* of type/dict/function versions: ITEMS = 1000 @@ -518,11 +548,6 @@ class TestRacesDoNotCrash(unittest.TestCase): WARMUPS = 2 WRITERS = 2 - def assert_specialized(self, f, opname): - instructions = dis.get_instructions(f, adaptive=True) - opnames = {instruction.opname for instruction in instructions} - self.assertIn(opname, opnames) - @disabling_optimizer def assert_races_do_not_crash( self, opname, get_items, read, write, *, check_items=False From abcc670b3423bd11c8a206cee594a77fcf8611d4 Mon Sep 17 00:00:00 2001 From: Matt Page Date: Mon, 19 Aug 2024 13:50:52 -0700 Subject: [PATCH 2/2] Inline checks for simple functions into the bytecode body This is the preferred style --- Python/bytecodes.c | 2 ++ Python/generated_cases.c.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 1a41c7e0d2dd99..cac9f67d7f2171 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3407,6 +3407,8 @@ dummy_func( PyFunctionObject *init = (PyFunctionObject *)cls->_spec_cache.init; PyCodeObject *code = (PyCodeObject *)init->func_code; DEOPT_IF(code->co_argcount != oparg+1); + DEOPT_IF((code->co_flags & (CO_VARKEYWORDS | CO_VARARGS | CO_OPTIMIZED)) != CO_OPTIMIZED); + DEOPT_IF(code->co_kwonlyargcount); DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize + _Py_InitCleanup.co_framesize)); STAT_INC(CALL, hit); PyObject *self = _PyType_NewManagedObject(tp); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 38a4c40e33ff22..fe42e20f4393d6 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -875,6 +875,8 @@ PyFunctionObject *init = (PyFunctionObject *)cls->_spec_cache.init; PyCodeObject *code = (PyCodeObject *)init->func_code; DEOPT_IF(code->co_argcount != oparg+1, CALL); + DEOPT_IF((code->co_flags & (CO_VARKEYWORDS | CO_VARARGS | CO_OPTIMIZED)) != CO_OPTIMIZED, CALL); + DEOPT_IF(code->co_kwonlyargcount, CALL); DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize + _Py_InitCleanup.co_framesize), CALL); STAT_INC(CALL, hit); PyObject *self = _PyType_NewManagedObject(tp);