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
@@ -249,7 +257,18 @@ typedef struct _gc_root_buffer {
249257} gc_root_buffer ;
250258
251259typedef struct _zend_gc_globals {
252- gc_root_buffer * buf ; /* preallocated arrays of buffers */
260+ /*
261+ * The root buffer, which stores possible roots of reference cycles. It is
262+ * also used to store garbage to be collected at the end of a run.
263+ * A single array which is reallocated as necessary.
264+ *
265+ * The lower two bits in each entry are used for flags and need to be masked
266+ * out to reconstruct a pointer.
267+ *
268+ * When an object in the root buffer is removed, the non-flag bits of the
269+ * unused entry are used to store the index of the next entry in the unused
270+ * list. */
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 objects 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 objects and freeing memory (ns) */
272291
273292 uint32_t dtor_idx ; /* root buffer index */
274293 uint32_t dtor_end ;
@@ -313,6 +332,8 @@ static zend_gc_globals gc_globals;
313332
314333typedef struct _gc_stack gc_stack ;
315334
335+ /*
336+ * The stack used for graph traversal is stored as a linked list of segments */
316337struct _gc_stack {
317338 gc_stack * prev ;
318339 gc_stack * next ;
@@ -375,6 +396,12 @@ static void gc_stack_free(gc_stack *stack)
375396 }
376397}
377398
399+ /*
400+ * Map a full index to a compressed index.
401+ *
402+ * The root buffer can have up to 2^30 entries, but we only have 20 bits to
403+ * store the index. So we use the 1<<19 as a compression flag and use the other
404+ * 19 bits to store the index modulo 2^19. */
378405static zend_always_inline uint32_t gc_compress (uint32_t idx )
379406{
380407 if (EXPECTED (idx < GC_MAX_UNCOMPRESSED )) {
@@ -383,6 +410,10 @@ static zend_always_inline uint32_t gc_compress(uint32_t idx)
383410 return (idx % GC_MAX_UNCOMPRESSED ) | GC_MAX_UNCOMPRESSED ;
384411}
385412
413+ /*
414+ * Find the root buffer entry given a pointer and a compressed index.
415+ * Iterate through the root buffer in steps of 2^19 until the pointer
416+ * matches. */
386417static zend_always_inline gc_root_buffer * gc_decompress (zend_refcounted * ref , uint32_t idx )
387418{
388419 gc_root_buffer * root = GC_IDX2PTR (idx );
@@ -401,6 +432,9 @@ static zend_always_inline gc_root_buffer* gc_decompress(zend_refcounted *ref, ui
401432 }
402433}
403434
435+ /*
436+ * Get the index of the next unused root buffer entry, and remove it from the
437+ * unused list. GC_HAS_UNUSED() must be true before calling this. */
404438static zend_always_inline uint32_t gc_fetch_unused (void )
405439{
406440 uint32_t idx ;
@@ -414,6 +448,7 @@ static zend_always_inline uint32_t gc_fetch_unused(void)
414448 return idx ;
415449}
416450
451+ /* Add a root buffer entry to the unused list */
417452static zend_always_inline void gc_link_unused (gc_root_buffer * root )
418453{
419454 root -> ref = GC_IDX2LIST (GC_G (unused ));
@@ -463,13 +498,15 @@ static void gc_trace_ref(zend_refcounted *ref) {
463498}
464499#endif
465500
501+ /* Mark a root buffer entry unused */
466502static zend_always_inline void gc_remove_from_roots (gc_root_buffer * root )
467503{
468504 GC_LINK_UNUSED (root );
469505 GC_G (num_roots )-- ;
470506 GC_BENCH_DEC (root_buf_length );
471507}
472508
509+ /* Destroy the root buffer */
473510static void root_buffer_dtor (zend_gc_globals * gc_globals )
474511{
475512 if (gc_globals -> buf ) {
@@ -565,6 +602,8 @@ void gc_reset(void)
565602 GC_G (activated_at ) = zend_hrtime ();
566603}
567604
605+ /* Enable/disable the garbage collector.
606+ * Initialize globals if necessary. */
568607ZEND_API bool gc_enable (bool enable )
569608{
570609 bool old_enabled = GC_G (gc_enabled );
@@ -584,6 +623,7 @@ ZEND_API bool gc_enabled(void)
584623 return GC_G (gc_enabled );
585624}
586625
626+ /* Protect the GC root buffer (prevent additions) */
587627ZEND_API bool gc_protect (bool protect )
588628{
589629 bool old_protected = GC_G (gc_protected );
@@ -596,6 +636,7 @@ ZEND_API bool gc_protected(void)
596636 return GC_G (gc_protected );
597637}
598638
639+ /* Reallocate the GC root buffer */
599640static void gc_grow_root_buffer (void )
600641{
601642 size_t new_size ;
@@ -621,6 +662,7 @@ static void gc_grow_root_buffer(void)
621662 GC_G (buf_size ) = new_size ;
622663}
623664
665+ /* Adjust the GC activation threshold given the number of objects collected by the last run */
624666static void gc_adjust_threshold (int count )
625667{
626668 uint32_t new_threshold ;
@@ -651,6 +693,7 @@ static void gc_adjust_threshold(int count)
651693 }
652694}
653695
696+ /* Add an object as a possible root, and perform a GC run unless one is active already. */
654697static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full (zend_refcounted * ref )
655698{
656699 uint32_t idx ;
@@ -695,6 +738,8 @@ static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full(zend_refc
695738 GC_BENCH_PEAK (root_buf_peak , root_buf_length );
696739}
697740
741+ /* Add a possible root object to the buffer.
742+ * Maybe perform a GC run. */
698743ZEND_API void ZEND_FASTCALL gc_possible_root (zend_refcounted * ref )
699744{
700745 uint32_t idx ;
@@ -731,6 +776,7 @@ ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
731776 GC_BENCH_PEAK (root_buf_peak , root_buf_length );
732777}
733778
779+ /* Add an extra root during a GC run */
734780static void ZEND_FASTCALL gc_extra_root (zend_refcounted * ref )
735781{
736782 uint32_t idx ;
@@ -764,12 +810,14 @@ static void ZEND_FASTCALL gc_extra_root(zend_refcounted *ref)
764810 GC_BENCH_PEAK (root_buf_peak , root_buf_length );
765811}
766812
813+ /* Remove an object from the root buffer given its compressed index */
767814static zend_never_inline void ZEND_FASTCALL gc_remove_compressed (zend_refcounted * ref , uint32_t idx )
768815{
769816 gc_root_buffer * root = gc_decompress (ref , idx );
770817 gc_remove_from_roots (root );
771818}
772819
820+ /* Remove an object from the root buffer */
773821ZEND_API void ZEND_FASTCALL gc_remove_from_buffer (zend_refcounted * ref )
774822{
775823 gc_root_buffer * root ;
@@ -793,6 +841,10 @@ ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref)
793841 gc_remove_from_roots (root );
794842}
795843
844+ /* Traverse the graph of objects referred to by ref. Change grey objects back
845+ * to black, and restore their reference counts. See ScanBlack() in Bacon & Rajan.
846+ * To implement a depth-first search, discovered objects are added to a stack which
847+ * is processed iteratively. */
796848static void gc_scan_black (zend_refcounted * ref , gc_stack * stack )
797849{
798850 HashTable * ht ;
@@ -992,6 +1044,8 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
9921044 }
9931045}
9941046
1047+ /* Traverse the graph of objects referred to by ref. Decrement the reference
1048+ * counts and mark visited objects grey. See MarkGray() in Bacon & Rajan. */
9951049static void gc_mark_grey (zend_refcounted * ref , gc_stack * stack )
9961050{
9971051 HashTable * ht ;
@@ -1204,6 +1258,8 @@ static void gc_compact(void)
12041258 }
12051259}
12061260
1261+ /* For all roots marked purple, traverse the graph, marking referred objects grey.
1262+ * See MarkRoots() in Bacon & Rajan. */
12071263static void gc_mark_roots (gc_stack * stack )
12081264{
12091265 gc_root_buffer * current , * last ;
@@ -1223,6 +1279,10 @@ static void gc_mark_roots(gc_stack *stack)
12231279 }
12241280}
12251281
1282+ /* Traverse the reference graph of ref. Evaluate grey nodes and mark them
1283+ * black (to keep) or white (to free). Note that nodes initially marked white
1284+ * may later become black if they are visited from a live node.
1285+ * See Scan() in Bacon & Rajan. */
12261286static void gc_scan (zend_refcounted * ref , gc_stack * stack )
12271287{
12281288 HashTable * ht ;
@@ -1376,6 +1436,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack)
13761436 }
13771437}
13781438
1439+ /* Scan all roots, coloring grey nodes black or white */
13791440static void gc_scan_roots (gc_stack * stack )
13801441{
13811442 uint32_t idx , end ;
@@ -1409,6 +1470,8 @@ static void gc_scan_roots(gc_stack *stack)
14091470 }
14101471}
14111472
1473+ /* Add an object to the buffer with the garbage flag, so that it will be
1474+ * destroyed and freed when the scan is complete. */
14121475static void gc_add_garbage (zend_refcounted * ref )
14131476{
14141477 uint32_t idx ;
@@ -1434,6 +1497,7 @@ static void gc_add_garbage(zend_refcounted *ref)
14341497 GC_G (num_roots )++ ;
14351498}
14361499
1500+ /* Traverse the reference graph from ref, marking any white objects as garbage. */
14371501static int gc_collect_white (zend_refcounted * ref , uint32_t * flags , gc_stack * stack )
14381502{
14391503 int count = 0 ;
@@ -1622,6 +1686,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta
16221686 return count ;
16231687}
16241688
1689+ /* Traverse the reference graph from all roots, marking white nodes as garbage. */
16251690static int gc_collect_roots (uint32_t * flags , gc_stack * stack )
16261691{
16271692 uint32_t idx , end ;
@@ -1808,6 +1873,7 @@ static ZEND_COLD ZEND_NORETURN void gc_start_destructor_fiber_error(void)
18081873 zend_error_noreturn (E_ERROR , "Unable to start destructor fiber" );
18091874}
18101875
1876+ /* Call destructors for garbage in the buffer. */
18111877static zend_always_inline zend_result gc_call_destructors (uint32_t idx , uint32_t end , zend_fiber * fiber )
18121878{
18131879 gc_root_buffer * current ;
@@ -1910,6 +1976,7 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
19101976 }
19111977}
19121978
1979+ /* Perform a garbage collection run. The default implementation of gc_collect_cycles. */
19131980ZEND_API int zend_gc_collect_cycles (void )
19141981{
19151982 int total_count = 0 ;
0 commit comments