Skip to content

Commit 271753a

Browse files
authored
bpo-35059: Convert _PyObject_GC_TRACK() to inline function (GH-10643)
* Add _PyObject_ASSERT_FROM() and _PyObject_ASSERT_FAILED_MSG() macros. * PyObject_GC_Track() now calls _PyObject_ASSERT_FAILED_MSG(), instead of Py_FatalError(), if the object is already tracked, to dump more information on error. * _PyObject_GC_TRACK() no longer checks if the object is already tracked at runtime, use an assertion instead for best performances; PyObject_GC_Track() still checks at runtime. * pycore_object.h now includes pycore_pystate.h. * Convert _PyObject_GC_TRACK() and _PyObject_GC_UNTRACK() macros to inline functions.
1 parent f1d002c commit 271753a

File tree

3 files changed

+68
-42
lines changed

3 files changed

+68
-42
lines changed

Include/internal/pycore_object.h

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE or Py_BUILD_CORE_BUILTIN defined"
99
#endif
1010

11+
#include "pycore_pystate.h" /* _PyRuntime */
12+
1113
/* Tell the GC to track this object.
1214
*
1315
* NB: While the object is tracked by the collector, it must be safe to call the
@@ -19,36 +21,56 @@ extern "C" {
1921
*
2022
* The PyObject_GC_Track() function is the public version of this macro.
2123
*/
22-
#define _PyObject_GC_TRACK(o) do { \
23-
PyGC_Head *g = _Py_AS_GC(o); \
24-
if (g->_gc_next != 0) { \
25-
Py_FatalError("GC object already tracked"); \
26-
} \
27-
assert((g->_gc_prev & _PyGC_PREV_MASK_COLLECTING) == 0); \
28-
PyGC_Head *last = (PyGC_Head*)(_PyRuntime.gc.generation0->_gc_prev); \
29-
_PyGCHead_SET_NEXT(last, g); \
30-
_PyGCHead_SET_PREV(g, last); \
31-
_PyGCHead_SET_NEXT(g, _PyRuntime.gc.generation0); \
32-
_PyRuntime.gc.generation0->_gc_prev = (uintptr_t)g; \
33-
} while (0);
24+
static inline void _PyObject_GC_TRACK_impl(const char *filename, int lineno,
25+
PyObject *op)
26+
{
27+
_PyObject_ASSERT_FROM(op, !_PyObject_GC_IS_TRACKED(op),
28+
"object already tracked by the garbage collector",
29+
filename, lineno, "_PyObject_GC_TRACK");
30+
31+
PyGC_Head *gc = _Py_AS_GC(op);
32+
_PyObject_ASSERT_FROM(op,
33+
(gc->_gc_prev & _PyGC_PREV_MASK_COLLECTING) == 0,
34+
"object is in generation which is garbage collected",
35+
filename, lineno, "_PyObject_GC_TRACK");
36+
37+
PyGC_Head *last = (PyGC_Head*)(_PyRuntime.gc.generation0->_gc_prev);
38+
_PyGCHead_SET_NEXT(last, gc);
39+
_PyGCHead_SET_PREV(gc, last);
40+
_PyGCHead_SET_NEXT(gc, _PyRuntime.gc.generation0);
41+
_PyRuntime.gc.generation0->_gc_prev = (uintptr_t)gc;
42+
}
43+
44+
#define _PyObject_GC_TRACK(op) \
45+
_PyObject_GC_TRACK_impl(__FILE__, __LINE__, (PyObject *)(op))
3446

3547
/* Tell the GC to stop tracking this object.
3648
*
37-
* Internal note: This may be called while GC. So _PyGC_PREV_MASK_COLLECTING must
38-
* be cleared. But _PyGC_PREV_MASK_FINALIZED bit is kept.
49+
* Internal note: This may be called while GC. So _PyGC_PREV_MASK_COLLECTING
50+
* must be cleared. But _PyGC_PREV_MASK_FINALIZED bit is kept.
51+
*
52+
* The object must be tracked by the GC.
3953
*
4054
* The PyObject_GC_UnTrack() function is the public version of this macro.
4155
*/
42-
#define _PyObject_GC_UNTRACK(o) do { \
43-
PyGC_Head *g = _Py_AS_GC(o); \
44-
PyGC_Head *prev = _PyGCHead_PREV(g); \
45-
PyGC_Head *next = _PyGCHead_NEXT(g); \
46-
assert(next != NULL); \
47-
_PyGCHead_SET_NEXT(prev, next); \
48-
_PyGCHead_SET_PREV(next, prev); \
49-
g->_gc_next = 0; \
50-
g->_gc_prev &= _PyGC_PREV_MASK_FINALIZED; \
51-
} while (0);
56+
static inline void _PyObject_GC_UNTRACK_impl(const char *filename, int lineno,
57+
PyObject *op)
58+
{
59+
_PyObject_ASSERT_FROM(op, _PyObject_GC_IS_TRACKED(op),
60+
"object not tracked by the garbage collector",
61+
filename, lineno, "_PyObject_GC_UNTRACK");
62+
63+
PyGC_Head *gc = _Py_AS_GC(op);
64+
PyGC_Head *prev = _PyGCHead_PREV(gc);
65+
PyGC_Head *next = _PyGCHead_NEXT(gc);
66+
_PyGCHead_SET_NEXT(prev, next);
67+
_PyGCHead_SET_PREV(next, prev);
68+
gc->_gc_next = 0;
69+
gc->_gc_prev &= _PyGC_PREV_MASK_FINALIZED;
70+
}
71+
72+
#define _PyObject_GC_UNTRACK(op) \
73+
_PyObject_GC_UNTRACK_impl(__FILE__, __LINE__, (PyObject *)(op))
5274

5375
#ifdef __cplusplus
5476
}

Include/object.h

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,7 @@ _PyObject_DebugTypeStats(FILE *out);
11361136

11371137
#ifndef Py_LIMITED_API
11381138
/* Define a pair of assertion macros:
1139-
_PyObject_ASSERT_WITH_MSG() and _PyObject_ASSERT().
1139+
_PyObject_ASSERT_FROM(), _PyObject_ASSERT_WITH_MSG() and _PyObject_ASSERT().
11401140
11411141
These work like the regular C assert(), in that they will abort the
11421142
process with a message on stderr if the given condition fails to hold,
@@ -1151,21 +1151,24 @@ _PyObject_DebugTypeStats(FILE *out);
11511151
will attempt to print to stderr, after the object dump. */
11521152
#ifdef NDEBUG
11531153
/* No debugging: compile away the assertions: */
1154-
# define _PyObject_ASSERT_WITH_MSG(obj, expr, msg) ((void)0)
1154+
# define _PyObject_ASSERT_FROM(obj, expr, msg, filename, lineno, func) \
1155+
((void)0)
11551156
#else
11561157
/* With debugging: generate checks: */
1157-
# define _PyObject_ASSERT_WITH_MSG(obj, expr, msg) \
1158-
((expr) \
1159-
? (void)(0) \
1160-
: _PyObject_AssertFailed((obj), \
1161-
Py_STRINGIFY(expr), \
1162-
(msg), \
1163-
__FILE__, \
1164-
__LINE__, \
1165-
__func__))
1158+
# define _PyObject_ASSERT_FROM(obj, expr, msg, filename, lineno, func) \
1159+
((expr) \
1160+
? (void)(0) \
1161+
: _PyObject_AssertFailed((obj), Py_STRINGIFY(expr), \
1162+
(msg), (filename), (lineno), (func)))
11661163
#endif
11671164

1168-
#define _PyObject_ASSERT(obj, expr) _PyObject_ASSERT_WITH_MSG(obj, expr, NULL)
1165+
#define _PyObject_ASSERT_WITH_MSG(obj, expr, msg) \
1166+
_PyObject_ASSERT_FROM(obj, expr, msg, __FILE__, __LINE__, __func__)
1167+
#define _PyObject_ASSERT(obj, expr) \
1168+
_PyObject_ASSERT_WITH_MSG(obj, expr, NULL)
1169+
1170+
#define _PyObject_ASSERT_FAILED_MSG(obj, msg) \
1171+
_PyObject_AssertFailed((obj), NULL, (msg), __FILE__, __LINE__, __func__)
11691172

11701173
/* Declare and define _PyObject_AssertFailed() even when NDEBUG is defined,
11711174
to avoid causing compiler/linker errors when building extensions without

Modules/gcmodule.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,15 +1846,16 @@ _PyGC_Dump(PyGC_Head *g)
18461846
/* extension modules might be compiled with GC support so these
18471847
functions must always be available */
18481848

1849-
#undef PyObject_GC_Track
1850-
#undef PyObject_GC_UnTrack
1851-
#undef PyObject_GC_Del
1852-
#undef _PyObject_GC_Malloc
1853-
18541849
void
18551850
PyObject_GC_Track(void *op)
18561851
{
1857-
_PyObject_GC_TRACK(op);
1852+
PyObject *obj = (PyObject *)op;
1853+
if (_PyObject_GC_IS_TRACKED(op)) {
1854+
_PyObject_ASSERT_FAILED_MSG(op,
1855+
"object already tracked "
1856+
"by the garbage collector");
1857+
}
1858+
_PyObject_GC_TRACK(obj);
18581859
}
18591860

18601861
void

0 commit comments

Comments
 (0)