gh-117139: Garbage collector support for deferred refcounting (#122956) · python/cpython@e001027 · GitHub
Skip to content

Commit e001027

Browse files
gh-117139: Garbage collector support for deferred refcounting (#122956)
The free-threaded GC now visits interpreter stacks to keep objects that use deferred reference counting alive. Interpreter frames are zero initialized in the free-threaded GC so that the GC doesn't see garbage data. This is a temporary measure until stack spilling around escaping calls is implemented. Co-authored-by: Ken Jin <kenjin@python.org>
1 parent 1dad23e commit e001027

6 files changed

Lines changed: 122 additions & 21 deletions

File tree

Include/internal/pycore_frame.h

Lines changed: 24 additions & 0 deletions

Include/internal/pycore_gc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ extern void _PyGC_ClearAllFreeLists(PyInterpreterState *interp);
381381
extern void _Py_ScheduleGC(PyThreadState *tstate);
382382
extern void _Py_RunGC(PyThreadState *tstate);
383383

384+
// GC visit callback for tracked interpreter frames
385+
extern int _PyGC_VisitFrameStack(struct _PyInterpreterFrame *frame, visitproc visit, void *arg);
384386

385387
#ifdef __cplusplus
386388
}

Include/internal/pycore_stackref.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,7 @@ PyStackRef_FromPyObjectNew(PyObject *obj)
150150
// Make sure we don't take an already tagged value.
151151
assert(((uintptr_t)obj & Py_TAG_BITS) == 0);
152152
assert(obj != NULL);
153-
// TODO (gh-117139): Add deferred objects later.
154-
if (_Py_IsImmortal(obj)) {
153+
if (_Py_IsImmortal(obj) || _PyObject_HasDeferredRefcount(obj)) {
155154
return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED };
156155
}
157156
else {
@@ -220,7 +219,8 @@ PyStackRef_DUP(_PyStackRef stackref)
220219
{
221220
if (PyStackRef_IsDeferred(stackref)) {
222221
assert(PyStackRef_IsNull(stackref) ||
223-
_Py_IsImmortal(PyStackRef_AsPyObjectBorrow(stackref)));
222+
_Py_IsImmortal(PyStackRef_AsPyObjectBorrow(stackref)) ||
223+
_PyObject_HasDeferredRefcount(PyStackRef_AsPyObjectBorrow(stackref)));
224224
return stackref;
225225
}
226226
Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref));

Python/frame.c

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,7 @@ _PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg)
1515
Py_VISIT(frame->f_locals);
1616
Py_VISIT(frame->f_funcobj);
1717
Py_VISIT(_PyFrame_GetCode(frame));
18-
/* locals */
19-
_PyStackRef *locals = _PyFrame_GetLocalsArray(frame);
20-
_PyStackRef *sp = frame->stackpointer;
21-
/* locals and stack */
22-
while (sp > locals) {
23-
sp--;
24-
Py_VISIT(PyStackRef_AsPyObjectBorrow(*sp));
25-
}
26-
return 0;
18+
return _PyGC_VisitFrameStack(frame, visit, arg);
2719
}
2820

2921
PyFrameObject *

Python/gc.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,17 @@ visit_decref(PyObject *op, void *parent)
534534
return 0;
535535
}
536536

537+
int
538+
_PyGC_VisitFrameStack(_PyInterpreterFrame *frame, visitproc visit, void *arg)
539+
{
540+
_PyStackRef *ref = _PyFrame_GetLocalsArray(frame);
541+
/* locals and stack */
542+
for (; ref < frame->stackpointer; ref++) {
543+
Py_VISIT(PyStackRef_AsPyObjectBorrow(*ref));
544+
}
545+
return 0;
546+
}
547+
537548
/* Subtract internal references from gc_refs. After this, gc_refs is >= 0
538549
* for all objects in containers, and is GC_REACHABLE for all tracked gc
539550
* objects not in containers. The ones with gc_refs > 0 are directly

Python/gc_free_threading.c

Lines changed: 81 additions & 9 deletions

0 commit comments

Comments
 (0)