Skip to content

Commit 3feb9c7

Browse files
committed
Add some comments to zend_gc.c
It took me a while to understand this code, so it makes sense to write some notes and share them.
1 parent fe88711 commit 3feb9c7

File tree

1 file changed

+74
-7
lines changed

1 file changed

+74
-7
lines changed

Zend/zend_gc.c

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@
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 */
@@ -228,10 +230,16 @@
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

251259
typedef 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

314333
typedef struct _gc_stack gc_stack;
315334

335+
/*
336+
* The stack used for graph traversal is stored as a linked list of segments */
316337
struct _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. */
378405
static 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. */
386417
static 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. */
404438
static 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 */
417452
static 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 */
466502
static 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 */
473510
static 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. */
568607
ZEND_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) */
587627
ZEND_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 */
599640
static 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 */
624666
static 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. */
654697
static 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. */
698743
ZEND_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 */
734780
static 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 */
767814
static 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 */
773821
ZEND_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. */
796848
static 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. */
9951049
static 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. */
12071263
static 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. */
12261286
static 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 */
13791440
static 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. */
14121475
static 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. */
14371501
static 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. */
16251690
static 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. */
18111877
static 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. */
19131980
ZEND_API int zend_gc_collect_cycles(void)
19141981
{
19151982
int total_count = 0;

0 commit comments

Comments
 (0)