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