172172#define GC_IDX2PTR (idx ) (GC_G(buf) + (idx))
173173#define GC_PTR2IDX (ptr ) ((ptr) - GC_G(buf))
174174
175+ /* Get the value to be placed in an unused buffer entry with the specified next unused list index */
175176#define GC_IDX2LIST (idx ) ((void*)(uintptr_t)(((idx) * sizeof(void*)) | GC_UNUSED))
177+ /* Get the index of the next item in the unused list from the given root buffer entry. */
176178#define GC_LIST2IDX (list ) (((uint32_t)(uintptr_t)(list)) / sizeof(void*))
177179
178180/* GC buffers */
228230} while (0)
229231
230232/* unused buffers */
233+
234+ /* Are there any unused root buffer entries? */
231235#define GC_HAS_UNUSED () \
232236 (GC_G(unused) != GC_INVALID)
237+
238+ /* Get the next unused entry and remove it from the list */
233239#define GC_FETCH_UNUSED () \
234240 gc_fetch_unused()
241+
242+ /* Add a root buffer entry to the unused list */
235243#define GC_LINK_UNUSED (root ) \
236244 gc_link_unused(root)
237245
244252
245253ZEND_API int (* gc_collect_cycles )(void );
246254
255+ /* The type of a root buffer entry.
256+ *
257+ * The lower two bits are used for flags and need to be masked out to
258+ * reconstruct a pointer.
259+ *
260+ * When a node in the root buffer is removed, the non-flag bits of the
261+ * unused entry are used to store the index of the next entry in the unused
262+ * list. */
247263typedef struct _gc_root_buffer {
248264 zend_refcounted * ref ;
249265} gc_root_buffer ;
250266
251267typedef struct _zend_gc_globals {
252- gc_root_buffer * buf ; /* preallocated arrays of buffers */
268+ /* The root buffer, which stores possible roots of reference cycles. It is
269+ * also used to store garbage to be collected at the end of a run.
270+ * A single array which is reallocated as necessary. */
271+ gc_root_buffer * buf ;
253272
254273 bool gc_enabled ;
255274 bool gc_active ; /* GC currently running, forbid nested GC */
@@ -262,13 +281,13 @@ typedef struct _zend_gc_globals {
262281 uint32_t buf_size ; /* size of the GC buffer */
263282 uint32_t num_roots ; /* number of roots in GC buffer */
264283
265- uint32_t gc_runs ;
266- uint32_t collected ;
284+ uint32_t gc_runs ; /* number of GC runs since reset */
285+ uint32_t collected ; /* number of collected nodes since reset */
267286
268- zend_hrtime_t activated_at ;
269- zend_hrtime_t collector_time ;
270- zend_hrtime_t dtor_time ;
271- zend_hrtime_t free_time ;
287+ zend_hrtime_t activated_at ; /* the timestamp of the last reset */
288+ zend_hrtime_t collector_time ; /* time spent running GC (ns) */
289+ zend_hrtime_t dtor_time ; /* time spent calling destructors (ns) */
290+ zend_hrtime_t free_time ; /* time spent destroying nodes and freeing memory (ns) */
272291
273292 uint32_t dtor_idx ; /* root buffer index */
274293 uint32_t dtor_end ;
@@ -313,6 +332,7 @@ static zend_gc_globals gc_globals;
313332
314333typedef struct _gc_stack gc_stack ;
315334
335+ /* The stack used for graph traversal is stored as a linked list of segments */
316336struct _gc_stack {
317337 gc_stack * prev ;
318338 gc_stack * next ;
@@ -375,6 +395,11 @@ static void gc_stack_free(gc_stack *stack)
375395 }
376396}
377397
398+ /* Map a full index to a compressed index.
399+ *
400+ * The root buffer can have up to 2^30 entries, but we only have 20 bits to
401+ * store the index. So we use the 1<<19 bit as a compression flag and use the
402+ * other 19 bits to store the index modulo 2^19. */
378403static zend_always_inline uint32_t gc_compress (uint32_t idx )
379404{
380405 if (EXPECTED (idx < GC_MAX_UNCOMPRESSED )) {
@@ -383,6 +408,9 @@ static zend_always_inline uint32_t gc_compress(uint32_t idx)
383408 return (idx % GC_MAX_UNCOMPRESSED ) | GC_MAX_UNCOMPRESSED ;
384409}
385410
411+ /* Find the root buffer entry given a pointer and a compressed index.
412+ * Iterate through the root buffer in steps of 2^19 until the pointer
413+ * matches. */
386414static zend_always_inline gc_root_buffer * gc_decompress (zend_refcounted * ref , uint32_t idx )
387415{
388416 gc_root_buffer * root = GC_IDX2PTR (idx );
@@ -401,6 +429,8 @@ static zend_always_inline gc_root_buffer* gc_decompress(zend_refcounted *ref, ui
401429 }
402430}
403431
432+ /* Get the index of the next unused root buffer entry, and remove it from the
433+ * unused list. GC_HAS_UNUSED() must be true before calling this. */
404434static zend_always_inline uint32_t gc_fetch_unused (void )
405435{
406436 uint32_t idx ;
@@ -414,6 +444,7 @@ static zend_always_inline uint32_t gc_fetch_unused(void)
414444 return idx ;
415445}
416446
447+ /* Add a root buffer entry to the unused list */
417448static zend_always_inline void gc_link_unused (gc_root_buffer * root )
418449{
419450 root -> ref = GC_IDX2LIST (GC_G (unused ));
@@ -463,6 +494,7 @@ static void gc_trace_ref(zend_refcounted *ref) {
463494}
464495#endif
465496
497+ /* Mark a root buffer entry unused */
466498static zend_always_inline void gc_remove_from_roots (gc_root_buffer * root )
467499{
468500 GC_LINK_UNUSED (root );
@@ -565,6 +597,8 @@ void gc_reset(void)
565597 GC_G (activated_at ) = zend_hrtime ();
566598}
567599
600+ /* Enable/disable the garbage collector.
601+ * Initialize globals if necessary. */
568602ZEND_API bool gc_enable (bool enable )
569603{
570604 bool old_enabled = GC_G (gc_enabled );
@@ -584,6 +618,7 @@ ZEND_API bool gc_enabled(void)
584618 return GC_G (gc_enabled );
585619}
586620
621+ /* Protect the GC root buffer (prevent additions) */
587622ZEND_API bool gc_protect (bool protect )
588623{
589624 bool old_protected = GC_G (gc_protected );
@@ -621,6 +656,7 @@ static void gc_grow_root_buffer(void)
621656 GC_G (buf_size ) = new_size ;
622657}
623658
659+ /* Adjust the GC activation threshold given the number of nodes collected by the last run */
624660static void gc_adjust_threshold (int count )
625661{
626662 uint32_t new_threshold ;
@@ -651,6 +687,7 @@ static void gc_adjust_threshold(int count)
651687 }
652688}
653689
690+ /* Perform a GC run and then add a node as a possible root. */
654691static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full (zend_refcounted * ref )
655692{
656693 uint32_t idx ;
@@ -695,6 +732,8 @@ static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full(zend_refc
695732 GC_BENCH_PEAK (root_buf_peak , root_buf_length );
696733}
697734
735+ /* Add a possible root node to the buffer.
736+ * Maybe perform a GC run. */
698737ZEND_API void ZEND_FASTCALL gc_possible_root (zend_refcounted * ref )
699738{
700739 uint32_t idx ;
@@ -731,6 +770,7 @@ ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
731770 GC_BENCH_PEAK (root_buf_peak , root_buf_length );
732771}
733772
773+ /* Add an extra root during a GC run */
734774static void ZEND_FASTCALL gc_extra_root (zend_refcounted * ref )
735775{
736776 uint32_t idx ;
@@ -764,6 +804,7 @@ static void ZEND_FASTCALL gc_extra_root(zend_refcounted *ref)
764804 GC_BENCH_PEAK (root_buf_peak , root_buf_length );
765805}
766806
807+ /* Remove a node from the root buffer given its compressed index */
767808static zend_never_inline void ZEND_FASTCALL gc_remove_compressed (zend_refcounted * ref , uint32_t idx )
768809{
769810 gc_root_buffer * root = gc_decompress (ref , idx );
@@ -793,6 +834,10 @@ ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref)
793834 gc_remove_from_roots (root );
794835}
795836
837+ /* Mark all nodes reachable from ref as black (live). Restore the reference
838+ * counts decremented by gc_mark_grey(). See ScanBlack() in Bacon & Rajan.
839+ * To implement a depth-first search, discovered nodes are added to a stack
840+ * which is processed iteratively. */
796841static void gc_scan_black (zend_refcounted * ref , gc_stack * stack )
797842{
798843 HashTable * ht ;
@@ -992,6 +1037,8 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
9921037 }
9931038}
9941039
1040+ /* Traverse the graph of nodes referred to by ref. Decrement the reference
1041+ * counts and mark visited nodes grey. See MarkGray() in Bacon & Rajan. */
9951042static void gc_mark_grey (zend_refcounted * ref , gc_stack * stack )
9961043{
9971044 HashTable * ht ;
@@ -1204,6 +1251,9 @@ static void gc_compact(void)
12041251 }
12051252}
12061253
1254+ /* For all roots marked purple, traverse the graph, decrementing the reference
1255+ * count of their child nodes. Mark visited nodes grey so that they are not
1256+ * visited again. See MarkRoots() in Bacon & Rajan. */
12071257static void gc_mark_roots (gc_stack * stack )
12081258{
12091259 gc_root_buffer * current , * last ;
@@ -1223,6 +1273,10 @@ static void gc_mark_roots(gc_stack *stack)
12231273 }
12241274}
12251275
1276+ /* Traverse the reference graph of ref. Evaluate grey nodes and mark them
1277+ * black (to keep) or white (to free). Note that nodes initially marked white
1278+ * may later become black if they are visited from a live node.
1279+ * See Scan() in Bacon & Rajan. */
12261280static void gc_scan (zend_refcounted * ref , gc_stack * stack )
12271281{
12281282 HashTable * ht ;
@@ -1376,6 +1430,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack)
13761430 }
13771431}
13781432
1433+ /* Scan all roots, coloring grey nodes black or white */
13791434static void gc_scan_roots (gc_stack * stack )
13801435{
13811436 uint32_t idx , end ;
@@ -1409,6 +1464,8 @@ static void gc_scan_roots(gc_stack *stack)
14091464 }
14101465}
14111466
1467+ /* Add a node to the buffer with the garbage flag, so that it will be
1468+ * destroyed and freed when the scan is complete. */
14121469static void gc_add_garbage (zend_refcounted * ref )
14131470{
14141471 uint32_t idx ;
@@ -1434,6 +1491,7 @@ static void gc_add_garbage(zend_refcounted *ref)
14341491 GC_G (num_roots )++ ;
14351492}
14361493
1494+ /* Traverse the reference graph from ref, marking any white nodes as garbage. */
14371495static int gc_collect_white (zend_refcounted * ref , uint32_t * flags , gc_stack * stack )
14381496{
14391497 int count = 0 ;
@@ -1622,6 +1680,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta
16221680 return count ;
16231681}
16241682
1683+ /* Traverse the reference graph from all roots, marking white nodes as garbage. */
16251684static int gc_collect_roots (uint32_t * flags , gc_stack * stack )
16261685{
16271686 uint32_t idx , end ;
@@ -1808,6 +1867,7 @@ static ZEND_COLD ZEND_NORETURN void gc_start_destructor_fiber_error(void)
18081867 zend_error_noreturn (E_ERROR , "Unable to start destructor fiber" );
18091868}
18101869
1870+ /* Call destructors for garbage in the buffer. */
18111871static zend_always_inline zend_result gc_call_destructors (uint32_t idx , uint32_t end , zend_fiber * fiber )
18121872{
18131873 gc_root_buffer * current ;
@@ -1910,6 +1970,7 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
19101970 }
19111971}
19121972
1973+ /* Perform a garbage collection run. The default implementation of gc_collect_cycles. */
19131974ZEND_API int zend_gc_collect_cycles (void )
19141975{
19151976 int total_count = 0 ;
0 commit comments