mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	gh-128807: Add marking phase for free-threaded cyclic GC (gh-128808)
This commit is contained in:
		
							parent
							
								
									8d8b854824
								
							
						
					
					
						commit
						080f444a58
					
				
					 3 changed files with 336 additions and 22 deletions
				
			
		|  | @ -45,12 +45,13 @@ static inline PyObject* _Py_FROM_GC(PyGC_Head *gc) { | ||||||
|  * the per-object lock. |  * the per-object lock. | ||||||
|  */ |  */ | ||||||
| #ifdef Py_GIL_DISABLED | #ifdef Py_GIL_DISABLED | ||||||
| #  define _PyGC_BITS_TRACKED        (1)     // Tracked by the GC
 | #  define _PyGC_BITS_TRACKED        (1<<0)     // Tracked by the GC
 | ||||||
| #  define _PyGC_BITS_FINALIZED      (2)     // tp_finalize was called
 | #  define _PyGC_BITS_FINALIZED      (1<<1)     // tp_finalize was called
 | ||||||
| #  define _PyGC_BITS_UNREACHABLE    (4) | #  define _PyGC_BITS_UNREACHABLE    (1<<2) | ||||||
| #  define _PyGC_BITS_FROZEN         (8) | #  define _PyGC_BITS_FROZEN         (1<<3) | ||||||
| #  define _PyGC_BITS_SHARED         (16) | #  define _PyGC_BITS_SHARED         (1<<4) | ||||||
| #  define _PyGC_BITS_DEFERRED       (64)    // Use deferred reference counting
 | #  define _PyGC_BITS_ALIVE          (1<<5)    // Reachable from a known root.
 | ||||||
|  | #  define _PyGC_BITS_DEFERRED       (1<<6)    // Use deferred reference counting
 | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef Py_GIL_DISABLED | #ifdef Py_GIL_DISABLED | ||||||
|  | @ -330,6 +331,9 @@ struct _gc_runtime_state { | ||||||
|        collections, and are awaiting to undergo a full collection for |        collections, and are awaiting to undergo a full collection for | ||||||
|        the first time. */ |        the first time. */ | ||||||
|     Py_ssize_t long_lived_pending; |     Py_ssize_t long_lived_pending; | ||||||
|  | 
 | ||||||
|  |     /* True if gc.freeze() has been used. */ | ||||||
|  |     int freeze_active; | ||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | Add a marking phase to the free-threaded GC.  This is similar to what was | ||||||
|  | done in GH-126491.  Since the free-threaded GC does not have generations and | ||||||
|  | is not incremental, the marking phase looks for all objects reachable from | ||||||
|  | known roots.  The roots are objects known to not be garbage, like the module | ||||||
|  | dictionary for :mod:`sys`.  For most programs, this marking phase should | ||||||
|  | make the GC a bit faster since typically less work is done per object. | ||||||
|  | @ -17,6 +17,17 @@ | ||||||
| #include "pydtrace.h" | #include "pydtrace.h" | ||||||
| #include "pycore_uniqueid.h"      // _PyObject_MergeThreadLocalRefcounts() | #include "pycore_uniqueid.h"      // _PyObject_MergeThreadLocalRefcounts() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | // enable the "mark alive" pass of GC
 | ||||||
|  | #define GC_ENABLE_MARK_ALIVE 1 | ||||||
|  | 
 | ||||||
|  | // include additional roots in "mark alive" pass
 | ||||||
|  | #define GC_MARK_ALIVE_EXTRA_ROOTS 1 | ||||||
|  | 
 | ||||||
|  | // include Python stacks as set of known roots
 | ||||||
|  | #define GC_MARK_ALIVE_STACKS 1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| #ifdef Py_GIL_DISABLED | #ifdef Py_GIL_DISABLED | ||||||
| 
 | 
 | ||||||
| typedef struct _gc_runtime_state GCState; | typedef struct _gc_runtime_state GCState; | ||||||
|  | @ -113,28 +124,66 @@ worklist_remove(struct worklist_iter *iter) | ||||||
|     iter->next = iter->ptr; |     iter->next = iter->ptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static inline int | ||||||
|  | gc_has_bit(PyObject *op, uint8_t bit) | ||||||
|  | { | ||||||
|  |     return (op->ob_gc_bits & bit) != 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void | ||||||
|  | gc_set_bit(PyObject *op, uint8_t bit) | ||||||
|  | { | ||||||
|  |     op->ob_gc_bits |= bit; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void | ||||||
|  | gc_clear_bit(PyObject *op, uint8_t bit) | ||||||
|  | { | ||||||
|  |     op->ob_gc_bits &= ~bit; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static inline int | static inline int | ||||||
| gc_is_frozen(PyObject *op) | gc_is_frozen(PyObject *op) | ||||||
| { | { | ||||||
|     return (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0; |     return gc_has_bit(op, _PyGC_BITS_FROZEN); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static inline int | static inline int | ||||||
| gc_is_unreachable(PyObject *op) | gc_is_unreachable(PyObject *op) | ||||||
| { | { | ||||||
|     return (op->ob_gc_bits & _PyGC_BITS_UNREACHABLE) != 0; |     return gc_has_bit(op, _PyGC_BITS_UNREACHABLE); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static inline void | ||||||
| gc_set_unreachable(PyObject *op) | gc_set_unreachable(PyObject *op) | ||||||
| { | { | ||||||
|     op->ob_gc_bits |= _PyGC_BITS_UNREACHABLE; |     gc_set_bit(op, _PyGC_BITS_UNREACHABLE); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static inline void | ||||||
| gc_clear_unreachable(PyObject *op) | gc_clear_unreachable(PyObject *op) | ||||||
| { | { | ||||||
|     op->ob_gc_bits &= ~_PyGC_BITS_UNREACHABLE; |     gc_clear_bit(op, _PyGC_BITS_UNREACHABLE); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline int | ||||||
|  | gc_is_alive(PyObject *op) | ||||||
|  | { | ||||||
|  |     return gc_has_bit(op, _PyGC_BITS_ALIVE); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef GC_ENABLE_MARK_ALIVE | ||||||
|  | static inline void | ||||||
|  | gc_set_alive(PyObject *op) | ||||||
|  | { | ||||||
|  |     gc_set_bit(op, _PyGC_BITS_ALIVE); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | static inline void | ||||||
|  | gc_clear_alive(PyObject *op) | ||||||
|  | { | ||||||
|  |     gc_clear_bit(op, _PyGC_BITS_ALIVE); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Initialize the `ob_tid` field to zero if the object is not already
 | // Initialize the `ob_tid` field to zero if the object is not already
 | ||||||
|  | @ -143,6 +192,7 @@ static void | ||||||
| gc_maybe_init_refs(PyObject *op) | gc_maybe_init_refs(PyObject *op) | ||||||
| { | { | ||||||
|     if (!gc_is_unreachable(op)) { |     if (!gc_is_unreachable(op)) { | ||||||
|  |         assert(!gc_is_alive(op)); | ||||||
|         gc_set_unreachable(op); |         gc_set_unreachable(op); | ||||||
|         op->ob_tid = 0; |         op->ob_tid = 0; | ||||||
|     } |     } | ||||||
|  | @ -264,9 +314,13 @@ static void | ||||||
| gc_restore_refs(PyObject *op) | gc_restore_refs(PyObject *op) | ||||||
| { | { | ||||||
|     if (gc_is_unreachable(op)) { |     if (gc_is_unreachable(op)) { | ||||||
|  |         assert(!gc_is_alive(op)); | ||||||
|         gc_restore_tid(op); |         gc_restore_tid(op); | ||||||
|         gc_clear_unreachable(op); |         gc_clear_unreachable(op); | ||||||
|     } |     } | ||||||
|  |     else { | ||||||
|  |         gc_clear_alive(op); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Given a mimalloc memory block return the PyObject stored in it or NULL if
 | // Given a mimalloc memory block return the PyObject stored in it or NULL if
 | ||||||
|  | @ -392,6 +446,119 @@ gc_visit_thread_stacks(PyInterpreterState *interp) | ||||||
|     _Py_FOR_EACH_TSTATE_END(interp); |     _Py_FOR_EACH_TSTATE_END(interp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Untrack objects that can never create reference cycles.
 | ||||||
|  | // Return true if the object was untracked.
 | ||||||
|  | static bool | ||||||
|  | gc_maybe_untrack(PyObject *op) | ||||||
|  | { | ||||||
|  |     // Currently we only check for tuples containing only non-GC objects.  In
 | ||||||
|  |     // theory we could check other immutable objects that contain references
 | ||||||
|  |     // to non-GC objects.
 | ||||||
|  |     if (PyTuple_CheckExact(op)) { | ||||||
|  |         _PyTuple_MaybeUntrack(op); | ||||||
|  |         if (!_PyObject_GC_IS_TRACKED(op)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef GC_ENABLE_MARK_ALIVE | ||||||
|  | static int | ||||||
|  | mark_alive_stack_push(PyObject *op, _PyObjectStack *stack) | ||||||
|  | { | ||||||
|  |     if (op == NULL) { | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |     if (!_PyObject_GC_IS_TRACKED(op)) { | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |     if (gc_is_alive(op)) { | ||||||
|  |         return 0; // already visited this object
 | ||||||
|  |     } | ||||||
|  |     if (gc_maybe_untrack(op)) { | ||||||
|  |         return 0; // was untracked, don't visit it
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Need to call tp_traverse on this object. Add to stack and mark it
 | ||||||
|  |     // alive so we don't traverse it a second time.
 | ||||||
|  |     gc_set_alive(op); | ||||||
|  |     if (_PyObjectStack_Push(stack, op) < 0) { | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool | ||||||
|  | gc_clear_alive_bits(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
|  |                     void *block, size_t block_size, void *args) | ||||||
|  | { | ||||||
|  |     PyObject *op = op_from_block(block, args, false); | ||||||
|  |     if (op == NULL) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     if (gc_is_alive(op)) { | ||||||
|  |         gc_clear_alive(op); | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | gc_abort_mark_alive(PyInterpreterState *interp, | ||||||
|  |                     struct collection_state *state, | ||||||
|  |                     _PyObjectStack *stack) | ||||||
|  | { | ||||||
|  |     // We failed to allocate memory for "stack" while doing the "mark
 | ||||||
|  |     // alive" phase.  In that case, free the object stack and make sure
 | ||||||
|  |     // that no objects have the alive bit set.
 | ||||||
|  |     _PyObjectStack_Clear(stack); | ||||||
|  |     gc_visit_heaps(interp, &gc_clear_alive_bits, &state->base); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef GC_MARK_ALIVE_STACKS | ||||||
|  | static int | ||||||
|  | gc_visit_stackref_mark_alive(_PyObjectStack *stack, _PyStackRef stackref) | ||||||
|  | { | ||||||
|  |     // Note: we MUST check that it is deferred before checking the rest.
 | ||||||
|  |     // Otherwise we might read into invalid memory due to non-deferred references
 | ||||||
|  |     // being dead already.
 | ||||||
|  |     if (PyStackRef_IsDeferred(stackref) && !PyStackRef_IsNull(stackref)) { | ||||||
|  |         PyObject *op = PyStackRef_AsPyObjectBorrow(stackref); | ||||||
|  |         if (mark_alive_stack_push(op, stack) < 0) { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int | ||||||
|  | gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, _PyObjectStack *stack) | ||||||
|  | { | ||||||
|  |     _Py_FOR_EACH_TSTATE_BEGIN(interp, p) { | ||||||
|  |         for (_PyInterpreterFrame *f = p->current_frame; f != NULL; f = f->previous) { | ||||||
|  |             PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable); | ||||||
|  |             if (executable == NULL || !PyCode_Check(executable)) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             PyCodeObject *co = (PyCodeObject *)executable; | ||||||
|  |             int max_stack = co->co_nlocalsplus + co->co_stacksize; | ||||||
|  |             if (gc_visit_stackref_mark_alive(stack, f->f_executable) < 0) { | ||||||
|  |                 return -1; | ||||||
|  |             } | ||||||
|  |             for (int i = 0; i < max_stack; i++) { | ||||||
|  |                 if (gc_visit_stackref_mark_alive(stack, f->localsplus[i]) < 0) { | ||||||
|  |                     return -1; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     _Py_FOR_EACH_TSTATE_END(interp); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | #endif // GC_MARK_ALIVE_STACKS
 | ||||||
|  | #endif // GC_ENABLE_MARK_ALIVE
 | ||||||
|  | 
 | ||||||
| static void | static void | ||||||
| queue_untracked_obj_decref(PyObject *op, struct collection_state *state) | queue_untracked_obj_decref(PyObject *op, struct collection_state *state) | ||||||
| { | { | ||||||
|  | @ -460,7 +627,8 @@ visit_decref(PyObject *op, void *arg) | ||||||
| { | { | ||||||
|     if (_PyObject_GC_IS_TRACKED(op) |     if (_PyObject_GC_IS_TRACKED(op) | ||||||
|         && !_Py_IsImmortal(op) |         && !_Py_IsImmortal(op) | ||||||
|         && !gc_is_frozen(op)) |         && !gc_is_frozen(op) | ||||||
|  |         && !gc_is_alive(op)) | ||||||
|     { |     { | ||||||
|         // If update_refs hasn't reached this object yet, mark it
 |         // If update_refs hasn't reached this object yet, mark it
 | ||||||
|         // as (tentatively) unreachable and initialize ob_tid to zero.
 |         // as (tentatively) unreachable and initialize ob_tid to zero.
 | ||||||
|  | @ -482,6 +650,10 @@ update_refs(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (gc_is_alive(op)) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // Exclude immortal objects from garbage collection
 |     // Exclude immortal objects from garbage collection
 | ||||||
|     if (_Py_IsImmortal(op)) { |     if (_Py_IsImmortal(op)) { | ||||||
|         op->ob_tid = 0; |         op->ob_tid = 0; | ||||||
|  | @ -497,14 +669,9 @@ update_refs(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
|     _PyObject_ASSERT(op, refcount >= 0); |     _PyObject_ASSERT(op, refcount >= 0); | ||||||
| 
 | 
 | ||||||
|     if (refcount > 0 && !_PyObject_HasDeferredRefcount(op)) { |     if (refcount > 0 && !_PyObject_HasDeferredRefcount(op)) { | ||||||
|         // Untrack tuples and dicts as necessary in this pass, but not objects
 |         if (gc_maybe_untrack(op)) { | ||||||
|         // with zero refcount, which we will want to collect.
 |             gc_restore_refs(op); | ||||||
|         if (PyTuple_CheckExact(op)) { |             return true; | ||||||
|             _PyTuple_MaybeUntrack(op); |  | ||||||
|             if (!_PyObject_GC_IS_TRACKED(op)) { |  | ||||||
|                 gc_restore_refs(op); |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -553,6 +720,21 @@ mark_reachable(PyObject *op) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #ifdef GC_DEBUG | #ifdef GC_DEBUG | ||||||
|  | static bool | ||||||
|  | validate_alive_bits(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
|  |                    void *block, size_t block_size, void *args) | ||||||
|  | { | ||||||
|  |     PyObject *op = op_from_block(block, args, false); | ||||||
|  |     if (op == NULL) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _PyObject_ASSERT_WITH_MSG(op, !gc_is_alive(op), | ||||||
|  |                               "object should not be marked as alive yet"); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static bool | static bool | ||||||
| validate_refcounts(const mi_heap_t *heap, const mi_heap_area_t *area, | validate_refcounts(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
|                    void *block, size_t block_size, void *args) |                    void *block, size_t block_size, void *args) | ||||||
|  | @ -586,6 +768,11 @@ validate_gc_objects(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (gc_is_alive(op)) { | ||||||
|  |         _PyObject_ASSERT(op, !gc_is_unreachable(op)); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     _PyObject_ASSERT(op, gc_is_unreachable(op)); |     _PyObject_ASSERT(op, gc_is_unreachable(op)); | ||||||
|     _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0, |     _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0, | ||||||
|                                   "refcount is too small"); |                                   "refcount is too small"); | ||||||
|  | @ -602,6 +789,10 @@ mark_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (gc_is_alive(op)) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0, |     _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0, | ||||||
|                                   "refcount is too small"); |                                   "refcount is too small"); | ||||||
| 
 | 
 | ||||||
|  | @ -630,6 +821,7 @@ restore_refs(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
|     } |     } | ||||||
|     gc_restore_tid(op); |     gc_restore_tid(op); | ||||||
|     gc_clear_unreachable(op); |     gc_clear_unreachable(op); | ||||||
|  |     gc_clear_alive(op); | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -679,6 +871,7 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
| 
 | 
 | ||||||
|     // object is reachable, restore `ob_tid`; we're done with these objects
 |     // object is reachable, restore `ob_tid`; we're done with these objects
 | ||||||
|     gc_restore_tid(op); |     gc_restore_tid(op); | ||||||
|  |     gc_clear_alive(op); | ||||||
|     state->long_lived_total++; |     state->long_lived_total++; | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  | @ -686,6 +879,89 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
| static int | static int | ||||||
| move_legacy_finalizer_reachable(struct collection_state *state); | move_legacy_finalizer_reachable(struct collection_state *state); | ||||||
| 
 | 
 | ||||||
|  | #ifdef GC_ENABLE_MARK_ALIVE | ||||||
|  | static int | ||||||
|  | propagate_alive_bits(_PyObjectStack *stack) | ||||||
|  | { | ||||||
|  |     for (;;) { | ||||||
|  |         PyObject *op = _PyObjectStack_Pop(stack); | ||||||
|  |         if (op == NULL) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         assert(_PyObject_GC_IS_TRACKED(op)); | ||||||
|  |         assert(gc_is_alive(op)); | ||||||
|  |         traverseproc traverse = Py_TYPE(op)->tp_traverse; | ||||||
|  |         if (traverse(op, (visitproc)&mark_alive_stack_push, stack) < 0) { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Using tp_traverse, mark everything reachable from known root objects
 | ||||||
|  | // (which must be non-garbage) as alive (_PyGC_BITS_ALIVE is set).  In
 | ||||||
|  | // most programs, this marks nearly all objects that are not actually
 | ||||||
|  | // unreachable.
 | ||||||
|  | //
 | ||||||
|  | // Actually alive objects can be missed in this pass if they are alive
 | ||||||
|  | // due to being referenced from an unknown root (e.g. an extension
 | ||||||
|  | // module global), some tp_traverse methods are either missing or not
 | ||||||
|  | // accurate, or objects that have been untracked.  Objects that are only
 | ||||||
|  | // reachable from the aforementioned are also missed.
 | ||||||
|  | //
 | ||||||
|  | // If gc.freeze() is used, this pass is disabled since it is unlikely to
 | ||||||
|  | // help much.  The next stages of cyclic GC will ignore objects with the
 | ||||||
|  | // alive bit set.
 | ||||||
|  | //
 | ||||||
|  | // Returns -1 on failure (out of memory).
 | ||||||
|  | static int | ||||||
|  | mark_alive_from_roots(PyInterpreterState *interp, | ||||||
|  |                       struct collection_state *state) | ||||||
|  | { | ||||||
|  | #ifdef GC_DEBUG | ||||||
|  |     // Check that all objects don't have alive bit set
 | ||||||
|  |     gc_visit_heaps(interp, &validate_alive_bits, &state->base); | ||||||
|  | #endif | ||||||
|  |     _PyObjectStack stack = { NULL }; | ||||||
|  | 
 | ||||||
|  |     #define STACK_PUSH(op) \ | ||||||
|  |         if (mark_alive_stack_push(op, &stack) < 0) { \ | ||||||
|  |             gc_abort_mark_alive(interp, state, &stack); \ | ||||||
|  |             return -1; \ | ||||||
|  |         } | ||||||
|  |     STACK_PUSH(interp->sysdict); | ||||||
|  | #ifdef GC_MARK_ALIVE_EXTRA_ROOTS | ||||||
|  |     STACK_PUSH(interp->builtins); | ||||||
|  |     STACK_PUSH(interp->dict); | ||||||
|  |     struct types_state *types = &interp->types; | ||||||
|  |     for (int i = 0; i < _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES; i++) { | ||||||
|  |         STACK_PUSH(types->builtins.initialized[i].tp_dict); | ||||||
|  |         STACK_PUSH(types->builtins.initialized[i].tp_subclasses); | ||||||
|  |     } | ||||||
|  |     for (int i = 0; i < _Py_MAX_MANAGED_STATIC_EXT_TYPES; i++) { | ||||||
|  |         STACK_PUSH(types->for_extensions.initialized[i].tp_dict); | ||||||
|  |         STACK_PUSH(types->for_extensions.initialized[i].tp_subclasses); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  | #ifdef GC_MARK_ALIVE_STACKS | ||||||
|  |     if (gc_visit_thread_stacks_mark_alive(interp, &stack) < 0) { | ||||||
|  |         gc_abort_mark_alive(interp, state, &stack); | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |     #undef STACK_PUSH | ||||||
|  | 
 | ||||||
|  |     // Use tp_traverse to find everything reachable from roots.
 | ||||||
|  |     if (propagate_alive_bits(&stack) < 0) { | ||||||
|  |         gc_abort_mark_alive(interp, state, &stack); | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | #endif // GC_ENABLE_MARK_ALIVE
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| static int | static int | ||||||
| deduce_unreachable_heap(PyInterpreterState *interp, | deduce_unreachable_heap(PyInterpreterState *interp, | ||||||
|                         struct collection_state *state) |                         struct collection_state *state) | ||||||
|  | @ -1245,6 +1521,25 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, | ||||||
| 
 | 
 | ||||||
|     process_delayed_frees(interp, state); |     process_delayed_frees(interp, state); | ||||||
| 
 | 
 | ||||||
|  |     #ifdef GC_ENABLE_MARK_ALIVE | ||||||
|  |     // If gc.freeze() was used, it seems likely that doing this "mark alive"
 | ||||||
|  |     // pass will not be a performance win.  Typically the majority of alive
 | ||||||
|  |     // objects will be marked as frozen and will be skipped anyhow, without
 | ||||||
|  |     // doing this extra work.  Doing this pass also defeats one of the
 | ||||||
|  |     // purposes of using freeze: avoiding writes to objects that are frozen.
 | ||||||
|  |     // So, we just skip this if gc.freeze() was used.
 | ||||||
|  |     if (!state->gcstate->freeze_active) { | ||||||
|  |         // Mark objects reachable from known roots as "alive".  These will
 | ||||||
|  |         // be ignored for rest of the GC pass.
 | ||||||
|  |         int err = mark_alive_from_roots(interp, state); | ||||||
|  |         if (err < 0) { | ||||||
|  |             _PyEval_StartTheWorld(interp); | ||||||
|  |             PyErr_NoMemory(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     #endif | ||||||
|  | 
 | ||||||
|     // Find unreachable objects
 |     // Find unreachable objects
 | ||||||
|     int err = deduce_unreachable_heap(interp, state); |     int err = deduce_unreachable_heap(interp, state); | ||||||
|     if (err < 0) { |     if (err < 0) { | ||||||
|  | @ -1253,6 +1548,11 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | #ifdef GC_DEBUG | ||||||
|  |     // At this point, no object should have the alive bit set
 | ||||||
|  |     gc_visit_heaps(interp, &validate_alive_bits, &state->base); | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|     // Print debugging information.
 |     // Print debugging information.
 | ||||||
|     if (interp->gc.debug & _PyGC_DEBUG_COLLECTABLE) { |     if (interp->gc.debug & _PyGC_DEBUG_COLLECTABLE) { | ||||||
|         PyObject *op; |         PyObject *op; | ||||||
|  | @ -1564,6 +1864,8 @@ _PyGC_Freeze(PyInterpreterState *interp) | ||||||
| { | { | ||||||
|     struct visitor_args args; |     struct visitor_args args; | ||||||
|     _PyEval_StopTheWorld(interp); |     _PyEval_StopTheWorld(interp); | ||||||
|  |     GCState *gcstate = get_gc_state(); | ||||||
|  |     gcstate->freeze_active = true; | ||||||
|     gc_visit_heaps(interp, &visit_freeze, &args); |     gc_visit_heaps(interp, &visit_freeze, &args); | ||||||
|     _PyEval_StartTheWorld(interp); |     _PyEval_StartTheWorld(interp); | ||||||
| } | } | ||||||
|  | @ -1574,7 +1876,7 @@ visit_unfreeze(const mi_heap_t *heap, const mi_heap_area_t *area, | ||||||
| { | { | ||||||
|     PyObject *op = op_from_block(block, args, true); |     PyObject *op = op_from_block(block, args, true); | ||||||
|     if (op != NULL) { |     if (op != NULL) { | ||||||
|         op->ob_gc_bits &= ~_PyGC_BITS_FROZEN; |         gc_clear_bit(op, _PyGC_BITS_FROZEN); | ||||||
|     } |     } | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  | @ -1584,6 +1886,8 @@ _PyGC_Unfreeze(PyInterpreterState *interp) | ||||||
| { | { | ||||||
|     struct visitor_args args; |     struct visitor_args args; | ||||||
|     _PyEval_StopTheWorld(interp); |     _PyEval_StopTheWorld(interp); | ||||||
|  |     GCState *gcstate = get_gc_state(); | ||||||
|  |     gcstate->freeze_active = false; | ||||||
|     gc_visit_heaps(interp, &visit_unfreeze, &args); |     gc_visit_heaps(interp, &visit_unfreeze, &args); | ||||||
|     _PyEval_StartTheWorld(interp); |     _PyEval_StartTheWorld(interp); | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Neil Schemenauer
						Neil Schemenauer