mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Add some comments to zend_gc.c
Closes GH-19412
This commit is contained in:
committed by
Arnaud Le Blanc
parent
cf33fd70ad
commit
38b9c336ba
@@ -172,7 +172,9 @@
|
||||
#define GC_IDX2PTR(idx) (GC_G(buf) + (idx))
|
||||
#define GC_PTR2IDX(ptr) ((ptr) - GC_G(buf))
|
||||
|
||||
/* Get the value to be placed in an unused buffer entry with the specified next unused list index */
|
||||
#define GC_IDX2LIST(idx) ((void*)(uintptr_t)(((idx) * sizeof(void*)) | GC_UNUSED))
|
||||
/* Get the index of the next item in the unused list from the given root buffer entry. */
|
||||
#define GC_LIST2IDX(list) (((uint32_t)(uintptr_t)(list)) / sizeof(void*))
|
||||
|
||||
/* GC buffers */
|
||||
@@ -228,10 +230,16 @@
|
||||
} while (0)
|
||||
|
||||
/* unused buffers */
|
||||
|
||||
/* Are there any unused root buffer entries? */
|
||||
#define GC_HAS_UNUSED() \
|
||||
(GC_G(unused) != GC_INVALID)
|
||||
|
||||
/* Get the next unused entry and remove it from the list */
|
||||
#define GC_FETCH_UNUSED() \
|
||||
gc_fetch_unused()
|
||||
|
||||
/* Add a root buffer entry to the unused list */
|
||||
#define GC_LINK_UNUSED(root) \
|
||||
gc_link_unused(root)
|
||||
|
||||
@@ -244,12 +252,23 @@
|
||||
|
||||
ZEND_API int (*gc_collect_cycles)(void);
|
||||
|
||||
/* The type of a root buffer entry.
|
||||
*
|
||||
* The lower two bits are used for flags and need to be masked out to
|
||||
* reconstruct a pointer.
|
||||
*
|
||||
* When a node in the root buffer is removed, the non-flag bits of the
|
||||
* unused entry are used to store the index of the next entry in the unused
|
||||
* list. */
|
||||
typedef struct _gc_root_buffer {
|
||||
zend_refcounted *ref;
|
||||
} gc_root_buffer;
|
||||
|
||||
typedef struct _zend_gc_globals {
|
||||
gc_root_buffer *buf; /* preallocated arrays of buffers */
|
||||
/* The root buffer, which stores possible roots of reference cycles. It is
|
||||
* also used to store garbage to be collected at the end of a run.
|
||||
* A single array which is reallocated as necessary. */
|
||||
gc_root_buffer *buf;
|
||||
|
||||
bool gc_enabled;
|
||||
bool gc_active; /* GC currently running, forbid nested GC */
|
||||
@@ -262,13 +281,13 @@ typedef struct _zend_gc_globals {
|
||||
uint32_t buf_size; /* size of the GC buffer */
|
||||
uint32_t num_roots; /* number of roots in GC buffer */
|
||||
|
||||
uint32_t gc_runs;
|
||||
uint32_t collected;
|
||||
uint32_t gc_runs; /* number of GC runs since reset */
|
||||
uint32_t collected; /* number of collected nodes since reset */
|
||||
|
||||
zend_hrtime_t activated_at;
|
||||
zend_hrtime_t collector_time;
|
||||
zend_hrtime_t dtor_time;
|
||||
zend_hrtime_t free_time;
|
||||
zend_hrtime_t activated_at; /* the timestamp of the last reset */
|
||||
zend_hrtime_t collector_time; /* time spent running GC (ns) */
|
||||
zend_hrtime_t dtor_time; /* time spent calling destructors (ns) */
|
||||
zend_hrtime_t free_time; /* time spent destroying nodes and freeing memory (ns) */
|
||||
|
||||
uint32_t dtor_idx; /* root buffer index */
|
||||
uint32_t dtor_end;
|
||||
@@ -313,6 +332,7 @@ static zend_gc_globals gc_globals;
|
||||
|
||||
typedef struct _gc_stack gc_stack;
|
||||
|
||||
/* The stack used for graph traversal is stored as a linked list of segments */
|
||||
struct _gc_stack {
|
||||
gc_stack *prev;
|
||||
gc_stack *next;
|
||||
@@ -375,6 +395,11 @@ static void gc_stack_free(gc_stack *stack)
|
||||
}
|
||||
}
|
||||
|
||||
/* Map a full index to a compressed index.
|
||||
*
|
||||
* The root buffer can have up to 2^30 entries, but we only have 20 bits to
|
||||
* store the index. So we use the 1<<19 bit as a compression flag and use the
|
||||
* other 19 bits to store the index modulo 2^19. */
|
||||
static zend_always_inline uint32_t gc_compress(uint32_t idx)
|
||||
{
|
||||
if (EXPECTED(idx < GC_MAX_UNCOMPRESSED)) {
|
||||
@@ -383,6 +408,9 @@ static zend_always_inline uint32_t gc_compress(uint32_t idx)
|
||||
return (idx % GC_MAX_UNCOMPRESSED) | GC_MAX_UNCOMPRESSED;
|
||||
}
|
||||
|
||||
/* Find the root buffer entry given a pointer and a compressed index.
|
||||
* Iterate through the root buffer in steps of 2^19 until the pointer
|
||||
* matches. */
|
||||
static zend_always_inline gc_root_buffer* gc_decompress(zend_refcounted *ref, uint32_t idx)
|
||||
{
|
||||
gc_root_buffer *root = GC_IDX2PTR(idx);
|
||||
@@ -401,6 +429,8 @@ static zend_always_inline gc_root_buffer* gc_decompress(zend_refcounted *ref, ui
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the index of the next unused root buffer entry, and remove it from the
|
||||
* unused list. GC_HAS_UNUSED() must be true before calling this. */
|
||||
static zend_always_inline uint32_t gc_fetch_unused(void)
|
||||
{
|
||||
uint32_t idx;
|
||||
@@ -414,6 +444,7 @@ static zend_always_inline uint32_t gc_fetch_unused(void)
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* Add a root buffer entry to the unused list */
|
||||
static zend_always_inline void gc_link_unused(gc_root_buffer *root)
|
||||
{
|
||||
root->ref = GC_IDX2LIST(GC_G(unused));
|
||||
@@ -463,6 +494,7 @@ static void gc_trace_ref(zend_refcounted *ref) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Mark a root buffer entry unused */
|
||||
static zend_always_inline void gc_remove_from_roots(gc_root_buffer *root)
|
||||
{
|
||||
GC_LINK_UNUSED(root);
|
||||
@@ -565,6 +597,8 @@ void gc_reset(void)
|
||||
GC_G(activated_at) = zend_hrtime();
|
||||
}
|
||||
|
||||
/* Enable/disable the garbage collector.
|
||||
* Initialize globals if necessary. */
|
||||
ZEND_API bool gc_enable(bool enable)
|
||||
{
|
||||
bool old_enabled = GC_G(gc_enabled);
|
||||
@@ -584,6 +618,7 @@ ZEND_API bool gc_enabled(void)
|
||||
return GC_G(gc_enabled);
|
||||
}
|
||||
|
||||
/* Protect the GC root buffer (prevent additions) */
|
||||
ZEND_API bool gc_protect(bool protect)
|
||||
{
|
||||
bool old_protected = GC_G(gc_protected);
|
||||
@@ -621,6 +656,7 @@ static void gc_grow_root_buffer(void)
|
||||
GC_G(buf_size) = new_size;
|
||||
}
|
||||
|
||||
/* Adjust the GC activation threshold given the number of nodes collected by the last run */
|
||||
static void gc_adjust_threshold(int count)
|
||||
{
|
||||
uint32_t new_threshold;
|
||||
@@ -651,6 +687,7 @@ static void gc_adjust_threshold(int count)
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform a GC run and then add a node as a possible root. */
|
||||
static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full(zend_refcounted *ref)
|
||||
{
|
||||
uint32_t idx;
|
||||
@@ -695,6 +732,8 @@ static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full(zend_refc
|
||||
GC_BENCH_PEAK(root_buf_peak, root_buf_length);
|
||||
}
|
||||
|
||||
/* Add a possible root node to the buffer.
|
||||
* Maybe perform a GC run. */
|
||||
ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
|
||||
{
|
||||
uint32_t idx;
|
||||
@@ -731,6 +770,7 @@ ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
|
||||
GC_BENCH_PEAK(root_buf_peak, root_buf_length);
|
||||
}
|
||||
|
||||
/* Add an extra root during a GC run */
|
||||
static void ZEND_FASTCALL gc_extra_root(zend_refcounted *ref)
|
||||
{
|
||||
uint32_t idx;
|
||||
@@ -764,6 +804,7 @@ static void ZEND_FASTCALL gc_extra_root(zend_refcounted *ref)
|
||||
GC_BENCH_PEAK(root_buf_peak, root_buf_length);
|
||||
}
|
||||
|
||||
/* Remove a node from the root buffer given its compressed index */
|
||||
static zend_never_inline void ZEND_FASTCALL gc_remove_compressed(zend_refcounted *ref, uint32_t idx)
|
||||
{
|
||||
gc_root_buffer *root = gc_decompress(ref, idx);
|
||||
@@ -793,6 +834,10 @@ ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref)
|
||||
gc_remove_from_roots(root);
|
||||
}
|
||||
|
||||
/* Mark all nodes reachable from ref as black (live). Restore the reference
|
||||
* counts decremented by gc_mark_grey(). See ScanBlack() in Bacon & Rajan.
|
||||
* To implement a depth-first search, discovered nodes are added to a stack
|
||||
* which is processed iteratively. */
|
||||
static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
|
||||
{
|
||||
HashTable *ht;
|
||||
@@ -992,6 +1037,8 @@ next:
|
||||
}
|
||||
}
|
||||
|
||||
/* Traverse the graph of nodes referred to by ref. Decrement the reference
|
||||
* counts and mark visited nodes grey. See MarkGray() in Bacon & Rajan. */
|
||||
static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
|
||||
{
|
||||
HashTable *ht;
|
||||
@@ -1204,6 +1251,9 @@ static void gc_compact(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* For all roots marked purple, traverse the graph, decrementing the reference
|
||||
* count of their child nodes. Mark visited nodes grey so that they are not
|
||||
* visited again. See MarkRoots() in Bacon & Rajan. */
|
||||
static void gc_mark_roots(gc_stack *stack)
|
||||
{
|
||||
gc_root_buffer *current, *last;
|
||||
@@ -1223,6 +1273,10 @@ static void gc_mark_roots(gc_stack *stack)
|
||||
}
|
||||
}
|
||||
|
||||
/* Traverse the reference graph of ref. Evaluate grey nodes and mark them
|
||||
* black (to keep) or white (to free). Note that nodes initially marked white
|
||||
* may later become black if they are visited from a live node.
|
||||
* See Scan() in Bacon & Rajan. */
|
||||
static void gc_scan(zend_refcounted *ref, gc_stack *stack)
|
||||
{
|
||||
HashTable *ht;
|
||||
@@ -1376,6 +1430,7 @@ next:
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan all roots, coloring grey nodes black or white */
|
||||
static void gc_scan_roots(gc_stack *stack)
|
||||
{
|
||||
uint32_t idx, end;
|
||||
@@ -1409,6 +1464,8 @@ static void gc_scan_roots(gc_stack *stack)
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a node to the buffer with the garbage flag, so that it will be
|
||||
* destroyed and freed when the scan is complete. */
|
||||
static void gc_add_garbage(zend_refcounted *ref)
|
||||
{
|
||||
uint32_t idx;
|
||||
@@ -1434,6 +1491,7 @@ static void gc_add_garbage(zend_refcounted *ref)
|
||||
GC_G(num_roots)++;
|
||||
}
|
||||
|
||||
/* Traverse the reference graph from ref, marking any white nodes as garbage. */
|
||||
static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *stack)
|
||||
{
|
||||
int count = 0;
|
||||
@@ -1622,6 +1680,7 @@ next:
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Traverse the reference graph from all roots, marking white nodes as garbage. */
|
||||
static int gc_collect_roots(uint32_t *flags, gc_stack *stack)
|
||||
{
|
||||
uint32_t idx, end;
|
||||
@@ -1808,6 +1867,7 @@ static ZEND_COLD ZEND_NORETURN void gc_start_destructor_fiber_error(void)
|
||||
zend_error_noreturn(E_ERROR, "Unable to start destructor fiber");
|
||||
}
|
||||
|
||||
/* Call destructors for garbage in the buffer. */
|
||||
static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t end, zend_fiber *fiber)
|
||||
{
|
||||
gc_root_buffer *current;
|
||||
@@ -1910,6 +1970,7 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform a garbage collection run. The default implementation of gc_collect_cycles. */
|
||||
ZEND_API int zend_gc_collect_cycles(void)
|
||||
{
|
||||
int total_count = 0;
|
||||
|
||||
Reference in New Issue
Block a user