From 42d3e11d8ced1bdccf170303a571dc2a1b708d9e Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 27 Apr 2023 13:50:27 -0700 Subject: [PATCH] code: make code object use deferred reference counting Code objects are frequently accessed by many threads, so enable deferred reference counting so that in the future we can skip ref count operations on code objects during frame evaluation. This requires making PyCode_Type support GC. --- Lib/test/test_capi/test_watchers.py | 3 ++- Lib/test/test_gc.py | 4 ++-- Objects/codeobject.c | 19 +++++++++++++++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_capi/test_watchers.py b/Lib/test/test_capi/test_watchers.py index 1922614ef60..af92430e50f 100644 --- a/Lib/test/test_capi/test_watchers.py +++ b/Lib/test/test_capi/test_watchers.py @@ -1,7 +1,7 @@ import unittest from contextlib import contextmanager, ExitStack -from test.support import catch_unraisable_exception, import_helper +from test.support import catch_unraisable_exception, import_helper, gc_collect # Skip this test if the _testcapi module isn't available. @@ -347,6 +347,7 @@ def code_watcher(self, which_watcher): def assert_event_counts(self, exp_created_0, exp_destroyed_0, exp_created_1, exp_destroyed_1): + gc_collect() self.assertEqual( exp_created_0, _testcapi.get_code_watcher_num_created_events(0)) self.assertEqual( diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index cf88a57c36b..650e36d4765 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -220,12 +220,12 @@ class B(object): def test_function(self): # Tricky: f -> d -> f, code should call d.clear() after the exec to - # break the cycle. + # break the cycle. May collect f.__code__ as well. d = {} exec("def f(): pass\n", d) gc.collect() del d - self.assertEqual(gc.collect(), 2) + self.assertTrue(2 <= gc.collect() <= 3) def test_function_tp_clear_leaves_consistent_state(self): # https://github.com/python/cpython/issues/91636 diff --git a/Objects/codeobject.c b/Objects/codeobject.c index ab31b6582cd..21dc136cdeb 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -6,6 +6,7 @@ #include "pycore_code.h" // _PyCodeConstructor #include "pycore_frame.h" // FRAME_SPECIALS_SIZE #include "pycore_interp.h" // PyInterpreterState.co_extra_freefuncs +#include "pycore_object.h" // _PyObject_SET_DEFERRED_REFCOUNT #include "pycore_opcode.h" // _PyOpcode_Deopt #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_tuple.h" // _PyTuple_ITEMS() @@ -550,13 +551,15 @@ _PyCode_New(struct _PyCodeConstructor *con) } Py_ssize_t size = PyBytes_GET_SIZE(con->code) / sizeof(_Py_CODEUNIT); - PyCodeObject *co = PyObject_NewVar(PyCodeObject, &PyCode_Type, size); + PyCodeObject *co = PyObject_GC_NewVar(PyCodeObject, &PyCode_Type, size); if (co == NULL) { Py_XDECREF(replacement_locations); PyErr_NoMemory(); return NULL; } init_code(co, con); + _PyObject_SET_DEFERRED_REFCOUNT(co); + _PyObject_GC_TRACK(co); Py_XDECREF(replacement_locations); return co; } @@ -1668,6 +1671,7 @@ code_dealloc(PyCodeObject *co) { notify_code_watchers(PY_CODE_EVENT_DESTROY, co); + _PyObject_GC_UNTRACK(co); if (co->co_extra != NULL) { PyInterpreterState *interp = _PyInterpreterState_GET(); _PyCodeObjectExtra *co_extra = co->co_extra; @@ -1705,7 +1709,14 @@ code_dealloc(PyCodeObject *co) if (co->_co_linearray) { PyMem_Free(co->_co_linearray); } - PyObject_Free(co); + PyObject_GC_Del(co); +} + +static int +code_traverse(PyCodeObject *co, visitproc visit, void *arg) +{ + Py_VISIT(co->co_consts); + return 0; } static PyObject * @@ -2114,9 +2125,9 @@ PyTypeObject PyCode_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /* tp_flags */ code_new__doc__, /* tp_doc */ - 0, /* tp_traverse */ + (traverseproc)code_traverse, /* tp_traverse */ 0, /* tp_clear */ code_richcompare, /* tp_richcompare */ offsetof(PyCodeObject, co_weakreflist), /* tp_weaklistoffset */