Skip to content

Commit 8bef34f

Browse files
authored
GH-117108: Set the "old space bit" to "visited" for all young objects (#117213)
Change old space bit of young objects from 0 to gcstate->visited_space. This ensures that any object created *and* collected during cycle GC has the bit set correctly.
1 parent bf82f77 commit 8bef34f

File tree

5 files changed

+56
-43
lines changed

5 files changed

+56
-43
lines changed

Include/internal/pycore_gc.h

+13-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,19 @@ static inline void _PyObject_GC_SET_SHARED_INLINE(PyObject *op) {
113113
/* Bit 1 is set when the object is in generation which is GCed currently. */
114114
#define _PyGC_PREV_MASK_COLLECTING 2
115115

116-
/* Bit 0 is set if the object belongs to old space 1 */
116+
/* Bit 0 in _gc_next is the old space bit.
117+
* It is set as follows:
118+
* Young: gcstate->visited_space
119+
* old[0]: 0
120+
* old[1]: 1
121+
* permanent: 0
122+
*
123+
* During a collection all objects handled should have the bit set to
124+
* gcstate->visited_space, as objects are moved from the young gen
125+
* and the increment into old[gcstate->visited_space].
126+
* When object are moved from the pending space, old[gcstate->visited_space^1]
127+
* into the increment, the old space bit is flipped.
128+
*/
117129
#define _PyGC_NEXT_MASK_OLD_SPACE_1 1
118130

119131
#define _PyGC_PREV_SHIFT 2

Include/internal/pycore_object.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,8 @@ static inline void _PyObject_GC_TRACK(
318318
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
319319
_PyGCHead_SET_NEXT(last, gc);
320320
_PyGCHead_SET_PREV(gc, last);
321-
_PyGCHead_SET_NEXT(gc, generation0);
322-
assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0);
321+
/* Young objects will be moved into the visited space during GC, so set the bit here */
322+
gc->_gc_next = ((uintptr_t)generation0) | interp->gc.visited_space;
323323
generation0->_gc_prev = (uintptr_t)gc;
324324
#endif
325325
}

Lib/test/test_gc.py

+1-23
Original file line numberDiff line numberDiff line change
@@ -823,32 +823,10 @@ def test_get_objects_generations(self):
823823
self.assertTrue(
824824
any(l is element for element in gc.get_objects(generation=0))
825825
)
826-
self.assertFalse(
827-
any(l is element for element in gc.get_objects(generation=1))
828-
)
829-
self.assertFalse(
830-
any(l is element for element in gc.get_objects(generation=2))
831-
)
832-
gc.collect(generation=0)
833-
self.assertFalse(
834-
any(l is element for element in gc.get_objects(generation=0))
835-
)
836-
self.assertTrue(
837-
any(l is element for element in gc.get_objects(generation=1))
838-
)
839-
self.assertFalse(
840-
any(l is element for element in gc.get_objects(generation=2))
841-
)
842-
gc.collect(generation=2)
826+
gc.collect()
843827
self.assertFalse(
844828
any(l is element for element in gc.get_objects(generation=0))
845829
)
846-
self.assertFalse(
847-
any(l is element for element in gc.get_objects(generation=1))
848-
)
849-
self.assertTrue(
850-
any(l is element for element in gc.get_objects(generation=2))
851-
)
852830
del l
853831
gc.collect()
854832

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Change the old space bit of objects in the young generation from 0 to
2+
gcstate->visited, so that any objects created during GC will have the old
3+
bit set correctly if they get moved into the old generation.

Python/gc.c

+37-17
Original file line numberDiff line numberDiff line change
@@ -455,10 +455,20 @@ validate_consistent_old_space(PyGC_Head *head)
455455
assert(prev == GC_PREV(head));
456456
}
457457

458+
static void
459+
gc_list_validate_space(PyGC_Head *head, int space) {
460+
PyGC_Head *gc = GC_NEXT(head);
461+
while (gc != head) {
462+
assert(gc_old_space(gc) == space);
463+
gc = GC_NEXT(gc);
464+
}
465+
}
466+
458467
#else
459468
#define validate_list(x, y) do{}while(0)
460469
#define validate_old(g) do{}while(0)
461470
#define validate_consistent_old_space(l) do{}while(0)
471+
#define gc_list_validate_space(l, s) do{}while(0)
462472
#endif
463473

464474
/*** end of list stuff ***/
@@ -949,6 +959,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
949959
/* Invoke the callbacks we decided to honor. It's safe to invoke them
950960
* because they can't reference unreachable objects.
951961
*/
962+
int visited_space = get_gc_state()->visited_space;
952963
while (! gc_list_is_empty(&wrcb_to_call)) {
953964
PyObject *temp;
954965
PyObject *callback;
@@ -983,6 +994,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
983994
Py_DECREF(op);
984995
if (wrcb_to_call._gc_next == (uintptr_t)gc) {
985996
/* object is still alive -- move it */
997+
gc_set_old_space(gc, visited_space);
986998
gc_list_move(gc, old);
987999
}
9881000
else {
@@ -1389,6 +1401,14 @@ completed_cycle(GCState *gcstate)
13891401
assert(gc_list_is_empty(not_visited));
13901402
#endif
13911403
gcstate->visited_space = flip_old_space(gcstate->visited_space);
1404+
/* Make sure all young objects have old space bit set correctly */
1405+
PyGC_Head *young = &gcstate->young.head;
1406+
PyGC_Head *gc = GC_NEXT(young);
1407+
while (gc != young) {
1408+
PyGC_Head *next = GC_NEXT(gc);
1409+
gc_set_old_space(gc, gcstate->visited_space);
1410+
gc = next;
1411+
}
13921412
gcstate->work_to_do = 0;
13931413
}
13941414

@@ -1406,10 +1426,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14061426
}
14071427
gc_list_merge(&gcstate->young.head, &increment);
14081428
gcstate->young.count = 0;
1409-
if (gcstate->visited_space) {
1410-
/* objects in visited space have bit set, so we set it here */
1411-
gc_list_set_space(&increment, 1);
1412-
}
1429+
gc_list_validate_space(&increment, gcstate->visited_space);
14131430
Py_ssize_t increment_size = 0;
14141431
while (increment_size < gcstate->work_to_do) {
14151432
if (gc_list_is_empty(not_visited)) {
@@ -1421,9 +1438,11 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14211438
gc_set_old_space(gc, gcstate->visited_space);
14221439
increment_size += expand_region_transitively_reachable(&increment, gc, gcstate);
14231440
}
1441+
gc_list_validate_space(&increment, gcstate->visited_space);
14241442
PyGC_Head survivors;
14251443
gc_list_init(&survivors);
14261444
gc_collect_region(tstate, &increment, &survivors, UNTRACK_TUPLES, stats);
1445+
gc_list_validate_space(&survivors, gcstate->visited_space);
14271446
gc_list_merge(&survivors, visited);
14281447
assert(gc_list_is_empty(&increment));
14291448
gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
@@ -1444,23 +1463,18 @@ gc_collect_full(PyThreadState *tstate,
14441463
GCState *gcstate = &tstate->interp->gc;
14451464
validate_old(gcstate);
14461465
PyGC_Head *young = &gcstate->young.head;
1447-
PyGC_Head *old0 = &gcstate->old[0].head;
1448-
PyGC_Head *old1 = &gcstate->old[1].head;
1449-
/* merge all generations into old0 */
1450-
gc_list_merge(young, old0);
1466+
PyGC_Head *pending = &gcstate->old[gcstate->visited_space^1].head;
1467+
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
1468+
/* merge all generations into visited */
1469+
gc_list_validate_space(young, gcstate->visited_space);
1470+
gc_list_set_space(pending, gcstate->visited_space);
1471+
gc_list_merge(young, pending);
14511472
gcstate->young.count = 0;
1452-
PyGC_Head *gc = GC_NEXT(old1);
1453-
while (gc != old1) {
1454-
PyGC_Head *next = GC_NEXT(gc);
1455-
gc_set_old_space(gc, 0);
1456-
gc = next;
1457-
}
1458-
gc_list_merge(old1, old0);
1473+
gc_list_merge(pending, visited);
14591474

1460-
gc_collect_region(tstate, old0, old0,
1475+
gc_collect_region(tstate, visited, visited,
14611476
UNTRACK_TUPLES | UNTRACK_DICTS,
14621477
stats);
1463-
gcstate->visited_space = 1;
14641478
gcstate->young.count = 0;
14651479
gcstate->old[0].count = 0;
14661480
gcstate->old[1].count = 0;
@@ -1527,6 +1541,7 @@ gc_collect_region(PyThreadState *tstate,
15271541

15281542
/* Clear weakrefs and invoke callbacks as necessary. */
15291543
stats->collected += handle_weakrefs(&unreachable, to);
1544+
gc_list_validate_space(to, gcstate->visited_space);
15301545
validate_list(to, collecting_clear_unreachable_clear);
15311546
validate_list(&unreachable, collecting_set_unreachable_clear);
15321547

@@ -1560,6 +1575,7 @@ gc_collect_region(PyThreadState *tstate,
15601575
* this if they insist on creating this type of structure.
15611576
*/
15621577
handle_legacy_finalizers(tstate, gcstate, &finalizers, to);
1578+
gc_list_validate_space(to, gcstate->visited_space);
15631579
validate_list(to, collecting_clear_unreachable_clear);
15641580
}
15651581

@@ -1708,6 +1724,10 @@ void
17081724
_PyGC_Freeze(PyInterpreterState *interp)
17091725
{
17101726
GCState *gcstate = &interp->gc;
1727+
/* The permanent_generation has its old space bit set to zero */
1728+
if (gcstate->visited_space) {
1729+
gc_list_set_space(&gcstate->young.head, 0);
1730+
}
17111731
gc_list_merge(&gcstate->young.head, &gcstate->permanent_generation.head);
17121732
gcstate->young.count = 0;
17131733
PyGC_Head*old0 = &gcstate->old[0].head;

0 commit comments

Comments
 (0)