Skip to content

gh-117376: Make code objects use deferred reference counting #117823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Lib/test/test_capi/test_watchers.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -372,6 +372,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() # code objects are collected by GC in free-threaded build
self.assertEqual(
exp_created_0, _testcapi.get_code_watcher_num_created_events(0))
self.assertEqual(
Expand Down Expand Up @@ -432,6 +433,7 @@ def test_dealloc_error(self):
with self.code_watcher(2):
with catch_unraisable_exception() as cm:
del co
gc_collect()

self.assertEqual(str(cm.unraisable.exc_value), "boom!")

Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,9 @@ def test_function(self):
exec("def f(): pass\n", d)
gc.collect()
del d
self.assertEqual(gc.collect(), 2)
# In the free-threaded build, the count returned by `gc.collect()`
# is 3 because it includes f's code object.
self.assertIn(gc.collect(), (2, 3))

def test_function_tp_clear_leaves_consistent_state(self):
# https://github.com/python/cpython/issues/91636
Expand Down
33 changes: 32 additions & 1 deletion Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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_SetDeferredRefcount
#include "pycore_opcode_metadata.h" // _PyOpcode_Deopt, _PyOpcode_Caches
#include "pycore_opcode_utils.h" // RESUME_AT_FUNC_START
#include "pycore_pystate.h" // _PyInterpreterState_GET()
Expand Down Expand Up @@ -557,13 +558,22 @@ _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;
#ifdef Py_GIL_DISABLED
co = PyObject_GC_NewVar(PyCodeObject, &PyCode_Type, size);
#else
co = PyObject_NewVar(PyCodeObject, &PyCode_Type, size);
#endif
if (co == NULL) {
Py_XDECREF(replacement_locations);
PyErr_NoMemory();
return NULL;
}
init_code(co, con);
#ifdef Py_GIL_DISABLED
_PyObject_SetDeferredRefcount((PyObject *)co);
_PyObject_GC_TRACK(co);
#endif
Py_XDECREF(replacement_locations);
return co;
}
Expand Down Expand Up @@ -1710,6 +1720,10 @@ code_dealloc(PyCodeObject *co)
}
Py_SET_REFCNT(co, 0);

#ifdef Py_GIL_DISABLED
PyObject_GC_UnTrack(co);
#endif

_PyFunction_ClearCodeByVersion(co->co_version);
if (co->co_extra != NULL) {
PyInterpreterState *interp = _PyInterpreterState_GET();
Expand Down Expand Up @@ -1752,6 +1766,15 @@ code_dealloc(PyCodeObject *co)
PyObject_Free(co);
}

#ifdef Py_GIL_DISABLED
static int
code_traverse(PyCodeObject *co, visitproc visit, void *arg)
{
Py_VISIT(co->co_consts);
return 0;
}
#endif

static PyObject *
code_repr(PyCodeObject *co)
{
Expand Down Expand Up @@ -2196,9 +2219,17 @@ PyTypeObject PyCode_Type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
#ifdef Py_GIL_DISABLED
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
#else
Py_TPFLAGS_DEFAULT, /* tp_flags */
#endif
code_new__doc__, /* tp_doc */
#ifdef Py_GIL_DISABLED
(traverseproc)code_traverse, /* tp_traverse */
#else
0, /* tp_traverse */
#endif
0, /* tp_clear */
code_richcompare, /* tp_richcompare */
offsetof(PyCodeObject, co_weakreflist), /* tp_weaklistoffset */
Expand Down