Skip to content

Commit 241ed5f

Browse files
authored
gh-117376: Make code objects use deferred reference counting (#117823)
We want code objects to use deferred reference counting in the free-threaded build. This requires them to be tracked by the GC, so we set `Py_TPFLAGS_HAVE_GC` in the free-threaded build, but not the default build.
1 parent a734fd5 commit 241ed5f

File tree

3 files changed

+38
-3
lines changed

3 files changed

+38
-3
lines changed

Diff for: Lib/test/test_capi/test_watchers.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import unittest
22

33
from contextlib import contextmanager, ExitStack
4-
from test.support import catch_unraisable_exception, import_helper
4+
from test.support import catch_unraisable_exception, import_helper, gc_collect
55

66

77
# Skip this test if the _testcapi module isn't available.
@@ -372,6 +372,7 @@ def code_watcher(self, which_watcher):
372372

373373
def assert_event_counts(self, exp_created_0, exp_destroyed_0,
374374
exp_created_1, exp_destroyed_1):
375+
gc_collect() # code objects are collected by GC in free-threaded build
375376
self.assertEqual(
376377
exp_created_0, _testcapi.get_code_watcher_num_created_events(0))
377378
self.assertEqual(
@@ -432,6 +433,7 @@ def test_dealloc_error(self):
432433
with self.code_watcher(2):
433434
with catch_unraisable_exception() as cm:
434435
del co
436+
gc_collect()
435437

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

Diff for: Lib/test/test_gc.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ def test_function(self):
226226
exec("def f(): pass\n", d)
227227
gc.collect()
228228
del d
229-
self.assertEqual(gc.collect(), 2)
229+
# In the free-threaded build, the count returned by `gc.collect()`
230+
# is 3 because it includes f's code object.
231+
self.assertIn(gc.collect(), (2, 3))
230232

231233
def test_function_tp_clear_leaves_consistent_state(self):
232234
# https://github.com/python/cpython/issues/91636

Diff for: Objects/codeobject.c

+32-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "pycore_code.h" // _PyCodeConstructor
77
#include "pycore_frame.h" // FRAME_SPECIALS_SIZE
88
#include "pycore_interp.h" // PyInterpreterState.co_extra_freefuncs
9+
#include "pycore_object.h" // _PyObject_SetDeferredRefcount
910
#include "pycore_opcode_metadata.h" // _PyOpcode_Deopt, _PyOpcode_Caches
1011
#include "pycore_opcode_utils.h" // RESUME_AT_FUNC_START
1112
#include "pycore_pystate.h" // _PyInterpreterState_GET()
@@ -557,13 +558,22 @@ _PyCode_New(struct _PyCodeConstructor *con)
557558
}
558559

559560
Py_ssize_t size = PyBytes_GET_SIZE(con->code) / sizeof(_Py_CODEUNIT);
560-
PyCodeObject *co = PyObject_NewVar(PyCodeObject, &PyCode_Type, size);
561+
PyCodeObject *co;
562+
#ifdef Py_GIL_DISABLED
563+
co = PyObject_GC_NewVar(PyCodeObject, &PyCode_Type, size);
564+
#else
565+
co = PyObject_NewVar(PyCodeObject, &PyCode_Type, size);
566+
#endif
561567
if (co == NULL) {
562568
Py_XDECREF(replacement_locations);
563569
PyErr_NoMemory();
564570
return NULL;
565571
}
566572
init_code(co, con);
573+
#ifdef Py_GIL_DISABLED
574+
_PyObject_SetDeferredRefcount((PyObject *)co);
575+
_PyObject_GC_TRACK(co);
576+
#endif
567577
Py_XDECREF(replacement_locations);
568578
return co;
569579
}
@@ -1710,6 +1720,10 @@ code_dealloc(PyCodeObject *co)
17101720
}
17111721
Py_SET_REFCNT(co, 0);
17121722

1723+
#ifdef Py_GIL_DISABLED
1724+
PyObject_GC_UnTrack(co);
1725+
#endif
1726+
17131727
_PyFunction_ClearCodeByVersion(co->co_version);
17141728
if (co->co_extra != NULL) {
17151729
PyInterpreterState *interp = _PyInterpreterState_GET();
@@ -1752,6 +1766,15 @@ code_dealloc(PyCodeObject *co)
17521766
PyObject_Free(co);
17531767
}
17541768

1769+
#ifdef Py_GIL_DISABLED
1770+
static int
1771+
code_traverse(PyCodeObject *co, visitproc visit, void *arg)
1772+
{
1773+
Py_VISIT(co->co_consts);
1774+
return 0;
1775+
}
1776+
#endif
1777+
17551778
static PyObject *
17561779
code_repr(PyCodeObject *co)
17571780
{
@@ -2196,9 +2219,17 @@ PyTypeObject PyCode_Type = {
21962219
PyObject_GenericGetAttr, /* tp_getattro */
21972220
0, /* tp_setattro */
21982221
0, /* tp_as_buffer */
2222+
#ifdef Py_GIL_DISABLED
2223+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
2224+
#else
21992225
Py_TPFLAGS_DEFAULT, /* tp_flags */
2226+
#endif
22002227
code_new__doc__, /* tp_doc */
2228+
#ifdef Py_GIL_DISABLED
2229+
(traverseproc)code_traverse, /* tp_traverse */
2230+
#else
22012231
0, /* tp_traverse */
2232+
#endif
22022233
0, /* tp_clear */
22032234
code_richcompare, /* tp_richcompare */
22042235
offsetof(PyCodeObject, co_weakreflist), /* tp_weaklistoffset */

0 commit comments

Comments
 (0)