1 /*
2 +----------------------------------------------------------------------+
3 | Zend Engine |
4 +----------------------------------------------------------------------+
5 | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 2.00 of the Zend license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.zend.com/license/2_00.txt. |
11 | If you did not receive a copy of the Zend license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@zend.com so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: David Wang <planetbeing@gmail.com> |
16 | Dmitry Stogov <dmitry@php.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 /**
21 * zend_gc_collect_cycles
22 * ======================
23 *
24 * Colors and its meaning
25 * ----------------------
26 *
27 * BLACK (GC_BLACK) - In use or free.
28 * GREY (GC_GREY) - Possible member of cycle.
29 * WHITE (GC_WHITE) - Member of garbage cycle.
30 * PURPLE (GC_PURPLE) - Possible root of cycle.
31 *
32 * Colors described in the paper but not used
33 * ------------------------------------------
34 *
35 * GREEN - Acyclic
36 * RED - Candidate cycle undergoing
37 * ORANGE - Candidate cycle awaiting epoch boundary.
38 *
39 *
40 * Flow
41 * =====
42 *
43 * The garbage collect cycle starts from 'gc_mark_roots', which traverses the
44 * possible roots, and calls mark_grey for roots are marked purple with
45 * depth-first traverse.
46 *
47 * After all possible roots are traversed and marked,
48 * gc_scan_roots will be called, and each root will be called with
49 * gc_scan(root->ref)
50 *
51 * gc_scan checks the colors of possible members.
52 *
53 * If the node is marked as grey and the refcount > 0
54 * gc_scan_black will be called on that node to scan it's subgraph.
55 * otherwise (refcount == 0), it marks the node white.
56 *
57 * A node MAY be added to possible roots when ZEND_UNSET_VAR happens or
58 * zend_assign_to_variable is called only when possible garbage node is
59 * produced.
60 * gc_possible_root() will be called to add the nodes to possible roots.
61 *
62 *
63 * For objects, we call their get_gc handler (by default 'zend_std_get_gc') to
64 * get the object properties to scan.
65 *
66 *
67 * @see http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
68 */
69 #include "zend.h"
70 #include "zend_API.h"
71 #include "zend_fibers.h"
72 #include "zend_hrtime.h"
73 #include "zend_weakrefs.h"
74
75 #ifndef GC_BENCH
76 # define GC_BENCH 0
77 #endif
78
79 #ifndef ZEND_GC_DEBUG
80 # define ZEND_GC_DEBUG 0
81 #endif
82
83 /* GC_INFO layout */
84 #define GC_ADDRESS 0x0fffffu
85 #define GC_COLOR 0x300000u
86
87 #define GC_BLACK 0x000000u /* must be zero */
88 #define GC_WHITE 0x100000u
89 #define GC_GREY 0x200000u
90 #define GC_PURPLE 0x300000u
91
92 /* Debug tracing */
93 #if ZEND_GC_DEBUG > 1
94 # define GC_TRACE(format, ...) fprintf(stderr, format "\n", ##__VA_ARGS__);
95 # define GC_TRACE_REF(ref, format, ...) \
96 do { \
97 gc_trace_ref((zend_refcounted *) ref); \
98 fprintf(stderr, format "\n", ##__VA_ARGS__); \
99 } while (0)
100 # define GC_TRACE_SET_COLOR(ref, color) \
101 GC_TRACE_REF(ref, "->%s", gc_color_name(color))
102 #else
103 # define GC_TRACE_REF(ref, format, ...)
104 # define GC_TRACE_SET_COLOR(ref, new_color)
105 # define GC_TRACE(str)
106 #endif
107
108 /* GC_INFO access */
109 #define GC_REF_ADDRESS(ref) \
110 (((GC_TYPE_INFO(ref)) & (GC_ADDRESS << GC_INFO_SHIFT)) >> GC_INFO_SHIFT)
111
112 #define GC_REF_COLOR(ref) \
113 (((GC_TYPE_INFO(ref)) & (GC_COLOR << GC_INFO_SHIFT)) >> GC_INFO_SHIFT)
114
115 #define GC_REF_CHECK_COLOR(ref, color) \
116 ((GC_TYPE_INFO(ref) & (GC_COLOR << GC_INFO_SHIFT)) == ((color) << GC_INFO_SHIFT))
117
118 #define GC_REF_SET_INFO(ref, info) do { \
119 GC_TYPE_INFO(ref) = \
120 (GC_TYPE_INFO(ref) & (GC_TYPE_MASK | GC_FLAGS_MASK)) | \
121 ((info) << GC_INFO_SHIFT); \
122 } while (0)
123
124 #define GC_REF_SET_COLOR(ref, c) do { \
125 GC_TRACE_SET_COLOR(ref, c); \
126 GC_TYPE_INFO(ref) = \
127 (GC_TYPE_INFO(ref) & ~(GC_COLOR << GC_INFO_SHIFT)) | \
128 ((c) << GC_INFO_SHIFT); \
129 } while (0)
130
131 #define GC_REF_SET_BLACK(ref) do { \
132 GC_TRACE_SET_COLOR(ref, GC_BLACK); \
133 GC_TYPE_INFO(ref) &= ~(GC_COLOR << GC_INFO_SHIFT); \
134 } while (0)
135
136 #define GC_REF_SET_PURPLE(ref) do { \
137 GC_TRACE_SET_COLOR(ref, GC_PURPLE); \
138 GC_TYPE_INFO(ref) |= (GC_COLOR << GC_INFO_SHIFT); \
139 } while (0)
140
141 /* bit stealing tags for gc_root_buffer.ref */
142 #define GC_BITS 0x3
143
144 #define GC_ROOT 0x0 /* possible root of circular garbage */
145 #define GC_UNUSED 0x1 /* part of linked list of unused buffers */
146 #define GC_GARBAGE 0x2 /* garbage to delete */
147 #define GC_DTOR_GARBAGE 0x3 /* garbage on which only the dtor should be invoked */
148
149 #define GC_GET_PTR(ptr) \
150 ((void*)(((uintptr_t)(ptr)) & ~GC_BITS))
151
152 #define GC_IS_ROOT(ptr) \
153 ((((uintptr_t)(ptr)) & GC_BITS) == GC_ROOT)
154 #define GC_IS_UNUSED(ptr) \
155 ((((uintptr_t)(ptr)) & GC_BITS) == GC_UNUSED)
156 #define GC_IS_GARBAGE(ptr) \
157 ((((uintptr_t)(ptr)) & GC_BITS) == GC_GARBAGE)
158 #define GC_IS_DTOR_GARBAGE(ptr) \
159 ((((uintptr_t)(ptr)) & GC_BITS) == GC_DTOR_GARBAGE)
160
161 #define GC_MAKE_GARBAGE(ptr) \
162 ((void*)(((uintptr_t)(ptr)) | GC_GARBAGE))
163 #define GC_MAKE_DTOR_GARBAGE(ptr) \
164 ((void*)(((uintptr_t)(ptr)) | GC_DTOR_GARBAGE))
165
166 /* GC address conversion */
167 #define GC_IDX2PTR(idx) (GC_G(buf) + (idx))
168 #define GC_PTR2IDX(ptr) ((ptr) - GC_G(buf))
169
170 #define GC_IDX2LIST(idx) ((void*)(uintptr_t)(((idx) * sizeof(void*)) | GC_UNUSED))
171 #define GC_LIST2IDX(list) (((uint32_t)(uintptr_t)(list)) / sizeof(void*))
172
173 /* GC buffers */
174 #define GC_INVALID 0
175 #define GC_FIRST_ROOT 1
176
177 #define GC_DEFAULT_BUF_SIZE (16 * 1024)
178 #define GC_BUF_GROW_STEP (128 * 1024)
179
180 #define GC_MAX_UNCOMPRESSED (512 * 1024)
181 #define GC_MAX_BUF_SIZE 0x40000000
182
183 #define GC_THRESHOLD_DEFAULT (10000 + GC_FIRST_ROOT)
184 #define GC_THRESHOLD_STEP 10000
185 #define GC_THRESHOLD_MAX 1000000000
186 #define GC_THRESHOLD_TRIGGER 100
187
188 /* GC flags */
189 #define GC_HAS_DESTRUCTORS (1<<0)
190
191 /* Weak maps */
192 #define Z_FROM_WEAKMAP_KEY (1<<0)
193 #define Z_FROM_WEAKMAP (1<<1)
194
195 /* The WeakMap entry zv is reachable from roots by following the virtual
196 * reference from the a WeakMap key to the entry */
197 #define GC_FROM_WEAKMAP_KEY(zv) \
198 (Z_TYPE_INFO_P((zv)) & (Z_FROM_WEAKMAP_KEY << Z_TYPE_INFO_EXTRA_SHIFT))
199
200 #define GC_SET_FROM_WEAKMAP_KEY(zv) do { \
201 zval *_z = (zv); \
202 Z_TYPE_INFO_P(_z) = Z_TYPE_INFO_P(_z) | (Z_FROM_WEAKMAP_KEY << Z_TYPE_INFO_EXTRA_SHIFT); \
203 } while (0)
204
205 #define GC_UNSET_FROM_WEAKMAP_KEY(zv) do { \
206 zval *_z = (zv); \
207 Z_TYPE_INFO_P(_z) = Z_TYPE_INFO_P(_z) & ~(Z_FROM_WEAKMAP_KEY << Z_TYPE_INFO_EXTRA_SHIFT); \
208 } while (0)
209
210 /* The WeakMap entry zv is reachable from roots by following the reference from
211 * the WeakMap */
212 #define GC_FROM_WEAKMAP(zv) \
213 (Z_TYPE_INFO_P((zv)) & (Z_FROM_WEAKMAP << Z_TYPE_INFO_EXTRA_SHIFT))
214
215 #define GC_SET_FROM_WEAKMAP(zv) do { \
216 zval *_z = (zv); \
217 Z_TYPE_INFO_P(_z) = Z_TYPE_INFO_P(_z) | (Z_FROM_WEAKMAP << Z_TYPE_INFO_EXTRA_SHIFT); \
218 } while (0)
219
220 #define GC_UNSET_FROM_WEAKMAP(zv) do { \
221 zval *_z = (zv); \
222 Z_TYPE_INFO_P(_z) = Z_TYPE_INFO_P(_z) & ~(Z_FROM_WEAKMAP << Z_TYPE_INFO_EXTRA_SHIFT); \
223 } while (0)
224
225 /* unused buffers */
226 #define GC_HAS_UNUSED() \
227 (GC_G(unused) != GC_INVALID)
228 #define GC_FETCH_UNUSED() \
229 gc_fetch_unused()
230 #define GC_LINK_UNUSED(root) \
231 gc_link_unused(root)
232
233 #define GC_HAS_NEXT_UNUSED_UNDER_THRESHOLD() \
234 (GC_G(first_unused) < GC_G(gc_threshold))
235 #define GC_HAS_NEXT_UNUSED() \
236 (GC_G(first_unused) != GC_G(buf_size))
237 #define GC_FETCH_NEXT_UNUSED() \
238 gc_fetch_next_unused()
239
240 ZEND_API int (*gc_collect_cycles)(void);
241
242 typedef struct _gc_root_buffer {
243 zend_refcounted *ref;
244 } gc_root_buffer;
245
246 typedef struct _zend_gc_globals {
247 gc_root_buffer *buf; /* preallocated arrays of buffers */
248
249 bool gc_enabled;
250 bool gc_active; /* GC currently running, forbid nested GC */
251 bool gc_protected; /* GC protected, forbid root additions */
252 bool gc_full;
253
254 uint32_t unused; /* linked list of unused buffers */
255 uint32_t first_unused; /* first unused buffer */
256 uint32_t gc_threshold; /* GC collection threshold */
257 uint32_t buf_size; /* size of the GC buffer */
258 uint32_t num_roots; /* number of roots in GC buffer */
259
260 uint32_t gc_runs;
261 uint32_t collected;
262
263 zend_hrtime_t activated_at;
264 zend_hrtime_t collector_time;
265 zend_hrtime_t dtor_time;
266 zend_hrtime_t free_time;
267
268 #if GC_BENCH
269 uint32_t root_buf_length;
270 uint32_t root_buf_peak;
271 uint32_t zval_possible_root;
272 uint32_t zval_buffered;
273 uint32_t zval_remove_from_buffer;
274 uint32_t zval_marked_grey;
275 #endif
276 } zend_gc_globals;
277
278 #ifdef ZTS
279 static int gc_globals_id;
280 static size_t gc_globals_offset;
281 #define GC_G(v) ZEND_TSRMG_FAST(gc_globals_offset, zend_gc_globals *, v)
282 #else
283 #define GC_G(v) (gc_globals.v)
284 static zend_gc_globals gc_globals;
285 #endif
286
287 #if GC_BENCH
288 # define GC_BENCH_INC(counter) GC_G(counter)++
289 # define GC_BENCH_DEC(counter) GC_G(counter)--
290 # define GC_BENCH_PEAK(peak, counter) do { \
291 if (GC_G(counter) > GC_G(peak)) { \
292 GC_G(peak) = GC_G(counter); \
293 } \
294 } while (0)
295 #else
296 # define GC_BENCH_INC(counter)
297 # define GC_BENCH_DEC(counter)
298 # define GC_BENCH_PEAK(peak, counter)
299 #endif
300
301
302 #define GC_STACK_SEGMENT_SIZE (((4096 - ZEND_MM_OVERHEAD) / sizeof(void*)) - 2)
303
304 typedef struct _gc_stack gc_stack;
305
306 struct _gc_stack {
307 gc_stack *prev;
308 gc_stack *next;
309 zend_refcounted *data[GC_STACK_SEGMENT_SIZE];
310 };
311
312 #define GC_STACK_DCL(init) \
313 gc_stack *_stack = init; \
314 size_t _top = 0;
315
316 #define GC_STACK_PUSH(ref) \
317 gc_stack_push(&_stack, &_top, ref);
318
319 #define GC_STACK_POP() \
320 gc_stack_pop(&_stack, &_top)
321
gc_stack_next(gc_stack * stack)322 static zend_never_inline gc_stack* gc_stack_next(gc_stack *stack)
323 {
324 if (UNEXPECTED(!stack->next)) {
325 gc_stack *segment = emalloc(sizeof(gc_stack));
326 segment->prev = stack;
327 segment->next = NULL;
328 stack->next = segment;
329 }
330 return stack->next;
331 }
332
gc_stack_push(gc_stack ** stack,size_t * top,zend_refcounted * ref)333 static zend_always_inline void gc_stack_push(gc_stack **stack, size_t *top, zend_refcounted *ref)
334 {
335 if (UNEXPECTED(*top == GC_STACK_SEGMENT_SIZE)) {
336 (*stack) = gc_stack_next(*stack);
337 (*top) = 0;
338 }
339 (*stack)->data[(*top)++] = ref;
340 }
341
gc_stack_pop(gc_stack ** stack,size_t * top)342 static zend_always_inline zend_refcounted* gc_stack_pop(gc_stack **stack, size_t *top)
343 {
344 if (UNEXPECTED((*top) == 0)) {
345 if (!(*stack)->prev) {
346 return NULL;
347 } else {
348 (*stack) = (*stack)->prev;
349 (*top) = GC_STACK_SEGMENT_SIZE - 1;
350 return (*stack)->data[GC_STACK_SEGMENT_SIZE - 1];
351 }
352 } else {
353 return (*stack)->data[--(*top)];
354 }
355 }
356
gc_stack_free(gc_stack * stack)357 static void gc_stack_free(gc_stack *stack)
358 {
359 gc_stack *p = stack->next;
360
361 while (p) {
362 stack = p->next;
363 efree(p);
364 p = stack;
365 }
366 }
367
gc_compress(uint32_t idx)368 static zend_always_inline uint32_t gc_compress(uint32_t idx)
369 {
370 if (EXPECTED(idx < GC_MAX_UNCOMPRESSED)) {
371 return idx;
372 }
373 return (idx % GC_MAX_UNCOMPRESSED) | GC_MAX_UNCOMPRESSED;
374 }
375
gc_decompress(zend_refcounted * ref,uint32_t idx)376 static zend_always_inline gc_root_buffer* gc_decompress(zend_refcounted *ref, uint32_t idx)
377 {
378 gc_root_buffer *root = GC_IDX2PTR(idx);
379
380 if (EXPECTED(GC_GET_PTR(root->ref) == ref)) {
381 return root;
382 }
383
384 while (1) {
385 idx += GC_MAX_UNCOMPRESSED;
386 ZEND_ASSERT(idx < GC_G(first_unused));
387 root = GC_IDX2PTR(idx);
388 if (GC_GET_PTR(root->ref) == ref) {
389 return root;
390 }
391 }
392 }
393
gc_fetch_unused(void)394 static zend_always_inline uint32_t gc_fetch_unused(void)
395 {
396 uint32_t idx;
397 gc_root_buffer *root;
398
399 ZEND_ASSERT(GC_HAS_UNUSED());
400 idx = GC_G(unused);
401 root = GC_IDX2PTR(idx);
402 ZEND_ASSERT(GC_IS_UNUSED(root->ref));
403 GC_G(unused) = GC_LIST2IDX(root->ref);
404 return idx;
405 }
406
gc_link_unused(gc_root_buffer * root)407 static zend_always_inline void gc_link_unused(gc_root_buffer *root)
408 {
409 root->ref = GC_IDX2LIST(GC_G(unused));
410 GC_G(unused) = GC_PTR2IDX(root);
411 }
412
gc_fetch_next_unused(void)413 static zend_always_inline uint32_t gc_fetch_next_unused(void)
414 {
415 uint32_t idx;
416
417 ZEND_ASSERT(GC_HAS_NEXT_UNUSED());
418 idx = GC_G(first_unused);
419 GC_G(first_unused) = GC_G(first_unused) + 1;
420 return idx;
421 }
422
423 #if ZEND_GC_DEBUG > 1
gc_color_name(uint32_t color)424 static const char *gc_color_name(uint32_t color) {
425 switch (color) {
426 case GC_BLACK: return "black";
427 case GC_WHITE: return "white";
428 case GC_GREY: return "grey";
429 case GC_PURPLE: return "purple";
430 default: return "unknown";
431 }
432 }
gc_trace_ref(zend_refcounted * ref)433 static void gc_trace_ref(zend_refcounted *ref) {
434 if (GC_TYPE(ref) == IS_OBJECT) {
435 zend_object *obj = (zend_object *) ref;
436 fprintf(stderr, "[%p] rc=%d addr=%d %s object(%s)#%d ",
437 ref, GC_REFCOUNT(ref), GC_REF_ADDRESS(ref),
438 gc_color_name(GC_REF_COLOR(ref)),
439 obj->ce->name->val, obj->handle);
440 } else if (GC_TYPE(ref) == IS_ARRAY) {
441 zend_array *arr = (zend_array *) ref;
442 fprintf(stderr, "[%p] rc=%d addr=%d %s array(%d) ",
443 ref, GC_REFCOUNT(ref), GC_REF_ADDRESS(ref),
444 gc_color_name(GC_REF_COLOR(ref)),
445 zend_hash_num_elements(arr));
446 } else {
447 fprintf(stderr, "[%p] rc=%d addr=%d %s %s ",
448 ref, GC_REFCOUNT(ref), GC_REF_ADDRESS(ref),
449 gc_color_name(GC_REF_COLOR(ref)),
450 GC_TYPE(ref) == IS_REFERENCE
451 ? "reference" : zend_get_type_by_const(GC_TYPE(ref)));
452 }
453 }
454 #endif
455
gc_remove_from_roots(gc_root_buffer * root)456 static zend_always_inline void gc_remove_from_roots(gc_root_buffer *root)
457 {
458 GC_LINK_UNUSED(root);
459 GC_G(num_roots)--;
460 GC_BENCH_DEC(root_buf_length);
461 }
462
root_buffer_dtor(zend_gc_globals * gc_globals)463 static void root_buffer_dtor(zend_gc_globals *gc_globals)
464 {
465 if (gc_globals->buf) {
466 free(gc_globals->buf);
467 gc_globals->buf = NULL;
468 }
469 }
470
gc_globals_ctor_ex(zend_gc_globals * gc_globals)471 static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
472 {
473 gc_globals->gc_enabled = 0;
474 gc_globals->gc_active = 0;
475 gc_globals->gc_protected = 1;
476 gc_globals->gc_full = 0;
477
478 gc_globals->buf = NULL;
479 gc_globals->unused = GC_INVALID;
480 gc_globals->first_unused = GC_INVALID;
481 gc_globals->gc_threshold = GC_INVALID;
482 gc_globals->buf_size = GC_INVALID;
483 gc_globals->num_roots = 0;
484
485 gc_globals->gc_runs = 0;
486 gc_globals->collected = 0;
487 gc_globals->collector_time = 0;
488 gc_globals->dtor_time = 0;
489 gc_globals->free_time = 0;
490 gc_globals->activated_at = 0;
491
492 #if GC_BENCH
493 gc_globals->root_buf_length = 0;
494 gc_globals->root_buf_peak = 0;
495 gc_globals->zval_possible_root = 0;
496 gc_globals->zval_buffered = 0;
497 gc_globals->zval_remove_from_buffer = 0;
498 gc_globals->zval_marked_grey = 0;
499 #endif
500 }
501
gc_globals_ctor(void)502 void gc_globals_ctor(void)
503 {
504 #ifdef ZTS
505 ts_allocate_fast_id(&gc_globals_id, &gc_globals_offset, sizeof(zend_gc_globals), (ts_allocate_ctor) gc_globals_ctor_ex, (ts_allocate_dtor) root_buffer_dtor);
506 #else
507 gc_globals_ctor_ex(&gc_globals);
508 #endif
509 }
510
gc_globals_dtor(void)511 void gc_globals_dtor(void)
512 {
513 #ifndef ZTS
514 root_buffer_dtor(&gc_globals);
515 #endif
516 }
517
gc_reset(void)518 void gc_reset(void)
519 {
520 if (GC_G(buf)) {
521 GC_G(gc_active) = 0;
522 GC_G(gc_protected) = 0;
523 GC_G(gc_full) = 0;
524 GC_G(unused) = GC_INVALID;
525 GC_G(first_unused) = GC_FIRST_ROOT;
526 GC_G(num_roots) = 0;
527
528 GC_G(gc_runs) = 0;
529 GC_G(collected) = 0;
530
531 GC_G(collector_time) = 0;
532 GC_G(dtor_time) = 0;
533 GC_G(free_time) = 0;
534
535 #if GC_BENCH
536 GC_G(root_buf_length) = 0;
537 GC_G(root_buf_peak) = 0;
538 GC_G(zval_possible_root) = 0;
539 GC_G(zval_buffered) = 0;
540 GC_G(zval_remove_from_buffer) = 0;
541 GC_G(zval_marked_grey) = 0;
542 #endif
543 }
544
545 GC_G(activated_at) = zend_hrtime();
546 }
547
gc_enable(bool enable)548 ZEND_API bool gc_enable(bool enable)
549 {
550 bool old_enabled = GC_G(gc_enabled);
551 GC_G(gc_enabled) = enable;
552 if (enable && !old_enabled && GC_G(buf) == NULL) {
553 GC_G(buf) = (gc_root_buffer*) pemalloc(sizeof(gc_root_buffer) * GC_DEFAULT_BUF_SIZE, 1);
554 GC_G(buf)[0].ref = NULL;
555 GC_G(buf_size) = GC_DEFAULT_BUF_SIZE;
556 GC_G(gc_threshold) = GC_THRESHOLD_DEFAULT;
557 gc_reset();
558 }
559 return old_enabled;
560 }
561
gc_enabled(void)562 ZEND_API bool gc_enabled(void)
563 {
564 return GC_G(gc_enabled);
565 }
566
gc_protect(bool protect)567 ZEND_API bool gc_protect(bool protect)
568 {
569 bool old_protected = GC_G(gc_protected);
570 GC_G(gc_protected) = protect;
571 return old_protected;
572 }
573
gc_protected(void)574 ZEND_API bool gc_protected(void)
575 {
576 return GC_G(gc_protected);
577 }
578
gc_grow_root_buffer(void)579 static void gc_grow_root_buffer(void)
580 {
581 size_t new_size;
582
583 if (GC_G(buf_size) >= GC_MAX_BUF_SIZE) {
584 if (!GC_G(gc_full)) {
585 zend_error(E_WARNING, "GC buffer overflow (GC disabled)\n");
586 GC_G(gc_active) = 1;
587 GC_G(gc_protected) = 1;
588 GC_G(gc_full) = 1;
589 return;
590 }
591 }
592 if (GC_G(buf_size) < GC_BUF_GROW_STEP) {
593 new_size = GC_G(buf_size) * 2;
594 } else {
595 new_size = GC_G(buf_size) + GC_BUF_GROW_STEP;
596 }
597 if (new_size > GC_MAX_BUF_SIZE) {
598 new_size = GC_MAX_BUF_SIZE;
599 }
600 GC_G(buf) = perealloc(GC_G(buf), sizeof(gc_root_buffer) * new_size, 1);
601 GC_G(buf_size) = new_size;
602 }
603
gc_adjust_threshold(int count)604 static void gc_adjust_threshold(int count)
605 {
606 uint32_t new_threshold;
607
608 /* TODO Very simple heuristic for dynamic GC buffer resizing:
609 * If there are "too few" collections, increase the collection threshold
610 * by a fixed step */
611 if (count < GC_THRESHOLD_TRIGGER || GC_G(num_roots) >= GC_G(gc_threshold)) {
612 /* increase */
613 if (GC_G(gc_threshold) < GC_THRESHOLD_MAX) {
614 new_threshold = GC_G(gc_threshold) + GC_THRESHOLD_STEP;
615 if (new_threshold > GC_THRESHOLD_MAX) {
616 new_threshold = GC_THRESHOLD_MAX;
617 }
618 if (new_threshold > GC_G(buf_size)) {
619 gc_grow_root_buffer();
620 }
621 if (new_threshold <= GC_G(buf_size)) {
622 GC_G(gc_threshold) = new_threshold;
623 }
624 }
625 } else if (GC_G(gc_threshold) > GC_THRESHOLD_DEFAULT) {
626 new_threshold = GC_G(gc_threshold) - GC_THRESHOLD_STEP;
627 if (new_threshold < GC_THRESHOLD_DEFAULT) {
628 new_threshold = GC_THRESHOLD_DEFAULT;
629 }
630 GC_G(gc_threshold) = new_threshold;
631 }
632 }
633
gc_possible_root_when_full(zend_refcounted * ref)634 static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full(zend_refcounted *ref)
635 {
636 uint32_t idx;
637 gc_root_buffer *newRoot;
638
639 ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT);
640 ZEND_ASSERT(GC_INFO(ref) == 0);
641
642 if (GC_G(gc_enabled) && !GC_G(gc_active)) {
643 GC_ADDREF(ref);
644 gc_adjust_threshold(gc_collect_cycles());
645 if (UNEXPECTED(GC_DELREF(ref) == 0)) {
646 rc_dtor_func(ref);
647 return;
648 } else if (UNEXPECTED(GC_INFO(ref))) {
649 return;
650 }
651 }
652
653 if (GC_HAS_UNUSED()) {
654 idx = GC_FETCH_UNUSED();
655 } else if (EXPECTED(GC_HAS_NEXT_UNUSED())) {
656 idx = GC_FETCH_NEXT_UNUSED();
657 } else {
658 gc_grow_root_buffer();
659 if (UNEXPECTED(!GC_HAS_NEXT_UNUSED())) {
660 return;
661 }
662 idx = GC_FETCH_NEXT_UNUSED();
663 }
664
665 newRoot = GC_IDX2PTR(idx);
666 newRoot->ref = ref; /* GC_ROOT tag is 0 */
667 GC_TRACE_SET_COLOR(ref, GC_PURPLE);
668
669 idx = gc_compress(idx);
670 GC_REF_SET_INFO(ref, idx | GC_PURPLE);
671 GC_G(num_roots)++;
672
673 GC_BENCH_INC(zval_buffered);
674 GC_BENCH_INC(root_buf_length);
675 GC_BENCH_PEAK(root_buf_peak, root_buf_length);
676 }
677
gc_possible_root(zend_refcounted * ref)678 ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
679 {
680 uint32_t idx;
681 gc_root_buffer *newRoot;
682
683 if (UNEXPECTED(GC_G(gc_protected))) {
684 return;
685 }
686
687 GC_BENCH_INC(zval_possible_root);
688
689 if (EXPECTED(GC_HAS_UNUSED())) {
690 idx = GC_FETCH_UNUSED();
691 } else if (EXPECTED(GC_HAS_NEXT_UNUSED_UNDER_THRESHOLD())) {
692 idx = GC_FETCH_NEXT_UNUSED();
693 } else {
694 gc_possible_root_when_full(ref);
695 return;
696 }
697
698 ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT);
699 ZEND_ASSERT(GC_INFO(ref) == 0);
700
701 newRoot = GC_IDX2PTR(idx);
702 newRoot->ref = ref; /* GC_ROOT tag is 0 */
703 GC_TRACE_SET_COLOR(ref, GC_PURPLE);
704
705 idx = gc_compress(idx);
706 GC_REF_SET_INFO(ref, idx | GC_PURPLE);
707 GC_G(num_roots)++;
708
709 GC_BENCH_INC(zval_buffered);
710 GC_BENCH_INC(root_buf_length);
711 GC_BENCH_PEAK(root_buf_peak, root_buf_length);
712 }
713
gc_extra_root(zend_refcounted * ref)714 static void ZEND_FASTCALL gc_extra_root(zend_refcounted *ref)
715 {
716 uint32_t idx;
717 gc_root_buffer *newRoot;
718
719 if (EXPECTED(GC_HAS_UNUSED())) {
720 idx = GC_FETCH_UNUSED();
721 } else if (EXPECTED(GC_HAS_NEXT_UNUSED())) {
722 idx = GC_FETCH_NEXT_UNUSED();
723 } else {
724 gc_grow_root_buffer();
725 if (UNEXPECTED(!GC_HAS_NEXT_UNUSED())) {
726 /* TODO: can this really happen? */
727 return;
728 }
729 idx = GC_FETCH_NEXT_UNUSED();
730 }
731
732 ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT);
733 ZEND_ASSERT(GC_REF_ADDRESS(ref) == 0);
734
735 newRoot = GC_IDX2PTR(idx);
736 newRoot->ref = ref; /* GC_ROOT tag is 0 */
737
738 idx = gc_compress(idx);
739 GC_REF_SET_INFO(ref, idx | GC_REF_COLOR(ref));
740 GC_G(num_roots)++;
741
742 GC_BENCH_INC(zval_buffered);
743 GC_BENCH_INC(root_buf_length);
744 GC_BENCH_PEAK(root_buf_peak, root_buf_length);
745 }
746
gc_remove_compressed(zend_refcounted * ref,uint32_t idx)747 static zend_never_inline void ZEND_FASTCALL gc_remove_compressed(zend_refcounted *ref, uint32_t idx)
748 {
749 gc_root_buffer *root = gc_decompress(ref, idx);
750 gc_remove_from_roots(root);
751 }
752
gc_remove_from_buffer(zend_refcounted * ref)753 ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref)
754 {
755 gc_root_buffer *root;
756 uint32_t idx = GC_REF_ADDRESS(ref);
757
758 GC_BENCH_INC(zval_remove_from_buffer);
759
760 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
761 GC_TRACE_SET_COLOR(ref, GC_BLACK);
762 }
763 GC_REF_SET_INFO(ref, 0);
764
765 /* Perform decompression only in case of large buffers */
766 if (UNEXPECTED(GC_G(first_unused) >= GC_MAX_UNCOMPRESSED)) {
767 gc_remove_compressed(ref, idx);
768 return;
769 }
770
771 ZEND_ASSERT(idx);
772 root = GC_IDX2PTR(idx);
773 gc_remove_from_roots(root);
774 }
775
gc_scan_black(zend_refcounted * ref,gc_stack * stack)776 static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
777 {
778 HashTable *ht;
779 Bucket *p;
780 zval *zv;
781 uint32_t n;
782 GC_STACK_DCL(stack);
783
784 tail_call:
785 if (GC_TYPE(ref) == IS_OBJECT) {
786 zend_object *obj = (zend_object*)ref;
787
788 if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) {
789 zval *table;
790 int len;
791
792 if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) {
793 zend_weakmap_get_object_key_entry_gc(obj, &table, &len);
794 n = len;
795 zv = table;
796 for (; n != 0; n-=2) {
797 ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
798 zval *entry = (zval*) Z_PTR_P(zv);
799 zval *weakmap = zv+1;
800 ZEND_ASSERT(Z_REFCOUNTED_P(weakmap));
801 if (Z_OPT_REFCOUNTED_P(entry)) {
802 GC_UNSET_FROM_WEAKMAP_KEY(entry);
803 if (GC_REF_CHECK_COLOR(Z_COUNTED_P(weakmap), GC_GREY)) {
804 /* Weakmap was scanned in gc_mark_roots, we must
805 * ensure that it's eventually scanned in
806 * gc_scan_roots as well. */
807 if (!GC_REF_ADDRESS(Z_COUNTED_P(weakmap))) {
808 gc_extra_root(Z_COUNTED_P(weakmap));
809 }
810 } else if (/* GC_REF_CHECK_COLOR(Z_COUNTED_P(weakmap), GC_BLACK) && */ !GC_FROM_WEAKMAP(entry)) {
811 /* Both the entry weakmap and key are BLACK, so we
812 * can mark the entry BLACK as well.
813 * !GC_FROM_WEAKMAP(entry) means that the weakmap
814 * was already scanned black (or will not be
815 * scanned), so it's our responsibility to mark the
816 * entry */
817 ZEND_ASSERT(GC_REF_CHECK_COLOR(Z_COUNTED_P(weakmap), GC_BLACK));
818 ref = Z_COUNTED_P(entry);
819 GC_ADDREF(ref);
820 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
821 GC_REF_SET_BLACK(ref);
822 GC_STACK_PUSH(ref);
823 }
824 }
825 }
826 zv+=2;
827 }
828 }
829
830 if (UNEXPECTED(obj->handlers->get_gc == zend_weakmap_get_gc)) {
831 zend_weakmap_get_key_entry_gc(obj, &table, &len);
832 n = len;
833 zv = table;
834 for (; n != 0; n-=2) {
835 ZEND_ASSERT(Z_TYPE_P(zv+1) == IS_PTR);
836 zval *key = zv;
837 zval *entry = (zval*) Z_PTR_P(zv+1);
838 if (Z_OPT_REFCOUNTED_P(entry)) {
839 GC_UNSET_FROM_WEAKMAP(entry);
840 if (GC_REF_CHECK_COLOR(Z_COUNTED_P(key), GC_GREY)) {
841 /* Key was scanned in gc_mark_roots, we must
842 * ensure that it's eventually scanned in
843 * gc_scan_roots as well. */
844 if (!GC_REF_ADDRESS(Z_COUNTED_P(key))) {
845 gc_extra_root(Z_COUNTED_P(key));
846 }
847 } else if (/* GC_REF_CHECK_COLOR(Z_COUNTED_P(key), GC_BLACK) && */ !GC_FROM_WEAKMAP_KEY(entry)) {
848 /* Both the entry weakmap and key are BLACK, so we
849 * can mark the entry BLACK as well.
850 * !GC_FROM_WEAKMAP_KEY(entry) means that the key
851 * was already scanned black (or will not be
852 * scanned), so it's our responsibility to mark the
853 * entry */
854 ZEND_ASSERT(GC_REF_CHECK_COLOR(Z_COUNTED_P(key), GC_BLACK));
855 ref = Z_COUNTED_P(entry);
856 GC_ADDREF(ref);
857 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
858 GC_REF_SET_BLACK(ref);
859 GC_STACK_PUSH(ref);
860 }
861 }
862 }
863 zv += 2;
864 }
865 goto next;
866 }
867
868 ht = obj->handlers->get_gc(obj, &table, &len);
869 n = len;
870 zv = table;
871 if (UNEXPECTED(ht)) {
872 GC_ADDREF(ht);
873 if (!GC_REF_CHECK_COLOR(ht, GC_BLACK)) {
874 GC_REF_SET_BLACK(ht);
875 for (; n != 0; n--) {
876 if (Z_REFCOUNTED_P(zv)) {
877 ref = Z_COUNTED_P(zv);
878 GC_ADDREF(ref);
879 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
880 GC_REF_SET_BLACK(ref);
881 GC_STACK_PUSH(ref);
882 }
883 }
884 zv++;
885 }
886 goto handle_ht;
887 }
888 }
889
890 handle_zvals:
891 for (; n != 0; n--) {
892 if (Z_REFCOUNTED_P(zv)) {
893 ref = Z_COUNTED_P(zv);
894 GC_ADDREF(ref);
895 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
896 GC_REF_SET_BLACK(ref);
897 zv++;
898 while (--n) {
899 if (Z_REFCOUNTED_P(zv)) {
900 zend_refcounted *ref = Z_COUNTED_P(zv);
901 GC_ADDREF(ref);
902 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
903 GC_REF_SET_BLACK(ref);
904 GC_STACK_PUSH(ref);
905 }
906 }
907 zv++;
908 }
909 goto tail_call;
910 }
911 }
912 zv++;
913 }
914 }
915 } else if (GC_TYPE(ref) == IS_ARRAY) {
916 ZEND_ASSERT((zend_array*)ref != &EG(symbol_table));
917 ht = (zend_array*)ref;
918 handle_ht:
919 n = ht->nNumUsed;
920 zv = ht->arPacked;
921 if (HT_IS_PACKED(ht)) {
922 goto handle_zvals;
923 }
924
925 p = (Bucket*)zv;
926 for (; n != 0; n--) {
927 zv = &p->val;
928 if (Z_TYPE_P(zv) == IS_INDIRECT) {
929 zv = Z_INDIRECT_P(zv);
930 }
931 if (Z_REFCOUNTED_P(zv)) {
932 ref = Z_COUNTED_P(zv);
933 GC_ADDREF(ref);
934 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
935 GC_REF_SET_BLACK(ref);
936 p++;
937 while (--n) {
938 zv = &p->val;
939 if (Z_TYPE_P(zv) == IS_INDIRECT) {
940 zv = Z_INDIRECT_P(zv);
941 }
942 if (Z_REFCOUNTED_P(zv)) {
943 zend_refcounted *ref = Z_COUNTED_P(zv);
944 GC_ADDREF(ref);
945 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
946 GC_REF_SET_BLACK(ref);
947 GC_STACK_PUSH(ref);
948 }
949 }
950 p++;
951 }
952 goto tail_call;
953 }
954 }
955 p++;
956 }
957 } else if (GC_TYPE(ref) == IS_REFERENCE) {
958 if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
959 ref = Z_COUNTED(((zend_reference*)ref)->val);
960 GC_ADDREF(ref);
961 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
962 GC_REF_SET_BLACK(ref);
963 goto tail_call;
964 }
965 }
966 }
967
968 next:
969 ref = GC_STACK_POP();
970 if (ref) {
971 goto tail_call;
972 }
973 }
974
gc_mark_grey(zend_refcounted * ref,gc_stack * stack)975 static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
976 {
977 HashTable *ht;
978 Bucket *p;
979 zval *zv;
980 uint32_t n;
981 GC_STACK_DCL(stack);
982
983 tail_call:
984 GC_BENCH_INC(zval_marked_grey);
985
986 if (GC_TYPE(ref) == IS_OBJECT) {
987 zend_object *obj = (zend_object*)ref;
988
989 if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) {
990 zval *table;
991 int len;
992
993 if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) {
994 zend_weakmap_get_object_key_entry_gc(obj, &table, &len);
995 n = len;
996 zv = table;
997 for (; n != 0; n-=2) {
998 ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
999 zval *entry = (zval*) Z_PTR_P(zv);
1000 zval *weakmap = zv+1;
1001 ZEND_ASSERT(Z_REFCOUNTED_P(weakmap));
1002 if (Z_REFCOUNTED_P(entry)) {
1003 GC_SET_FROM_WEAKMAP_KEY(entry);
1004 ref = Z_COUNTED_P(entry);
1005 /* Only DELREF if the contribution from the weakmap has
1006 * not been cancelled yet */
1007 if (!GC_FROM_WEAKMAP(entry)) {
1008 GC_DELREF(ref);
1009 }
1010 if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1011 GC_REF_SET_COLOR(ref, GC_GREY);
1012 GC_STACK_PUSH(ref);
1013 }
1014 }
1015 zv+=2;
1016 }
1017 }
1018
1019 if (UNEXPECTED(obj->handlers->get_gc == zend_weakmap_get_gc)) {
1020 zend_weakmap_get_entry_gc(obj, &table, &len);
1021 n = len;
1022 zv = table;
1023 for (; n != 0; n--) {
1024 ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
1025 zval *entry = (zval*) Z_PTR_P(zv);
1026 if (Z_REFCOUNTED_P(entry)) {
1027 GC_SET_FROM_WEAKMAP(entry);
1028 ref = Z_COUNTED_P(entry);
1029 /* Only DELREF if the contribution from the weakmap key
1030 * has not been cancelled yet */
1031 if (!GC_FROM_WEAKMAP_KEY(entry)) {
1032 GC_DELREF(ref);
1033 }
1034 if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1035 GC_REF_SET_COLOR(ref, GC_GREY);
1036 GC_STACK_PUSH(ref);
1037 }
1038 }
1039 zv++;
1040 }
1041 goto next;
1042 }
1043
1044 ht = obj->handlers->get_gc(obj, &table, &len);
1045 n = len;
1046 zv = table;
1047 if (UNEXPECTED(ht)) {
1048 GC_DELREF(ht);
1049 if (!GC_REF_CHECK_COLOR(ht, GC_GREY)) {
1050 GC_REF_SET_COLOR(ht, GC_GREY);
1051 for (; n != 0; n--) {
1052 if (Z_REFCOUNTED_P(zv)) {
1053 ref = Z_COUNTED_P(zv);
1054 GC_DELREF(ref);
1055 if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1056 GC_REF_SET_COLOR(ref, GC_GREY);
1057 GC_STACK_PUSH(ref);
1058 }
1059 }
1060 zv++;
1061 }
1062 goto handle_ht;
1063 }
1064 }
1065 handle_zvals:
1066 for (; n != 0; n--) {
1067 if (Z_REFCOUNTED_P(zv)) {
1068 ref = Z_COUNTED_P(zv);
1069 GC_DELREF(ref);
1070 if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1071 GC_REF_SET_COLOR(ref, GC_GREY);
1072 zv++;
1073 while (--n) {
1074 if (Z_REFCOUNTED_P(zv)) {
1075 zend_refcounted *ref = Z_COUNTED_P(zv);
1076 GC_DELREF(ref);
1077 if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1078 GC_REF_SET_COLOR(ref, GC_GREY);
1079 GC_STACK_PUSH(ref);
1080 }
1081 }
1082 zv++;
1083 }
1084 goto tail_call;
1085 }
1086 }
1087 zv++;
1088 }
1089 }
1090 } else if (GC_TYPE(ref) == IS_ARRAY) {
1091 ZEND_ASSERT(((zend_array*)ref) != &EG(symbol_table));
1092 ht = (zend_array*)ref;
1093 handle_ht:
1094 n = ht->nNumUsed;
1095 if (HT_IS_PACKED(ht)) {
1096 zv = ht->arPacked;
1097 goto handle_zvals;
1098 }
1099
1100 p = ht->arData;
1101 for (; n != 0; n--) {
1102 zv = &p->val;
1103 if (Z_TYPE_P(zv) == IS_INDIRECT) {
1104 zv = Z_INDIRECT_P(zv);
1105 }
1106 if (Z_REFCOUNTED_P(zv)) {
1107 ref = Z_COUNTED_P(zv);
1108 GC_DELREF(ref);
1109 if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1110 GC_REF_SET_COLOR(ref, GC_GREY);
1111 p++;
1112 while (--n) {
1113 zv = &p->val;
1114 if (Z_TYPE_P(zv) == IS_INDIRECT) {
1115 zv = Z_INDIRECT_P(zv);
1116 }
1117 if (Z_REFCOUNTED_P(zv)) {
1118 zend_refcounted *ref = Z_COUNTED_P(zv);
1119 GC_DELREF(ref);
1120 if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1121 GC_REF_SET_COLOR(ref, GC_GREY);
1122 GC_STACK_PUSH(ref);
1123 }
1124 }
1125 p++;
1126 }
1127 goto tail_call;
1128 }
1129 }
1130 p++;
1131 }
1132 } else if (GC_TYPE(ref) == IS_REFERENCE) {
1133 if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
1134 ref = Z_COUNTED(((zend_reference*)ref)->val);
1135 GC_DELREF(ref);
1136 if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1137 GC_REF_SET_COLOR(ref, GC_GREY);
1138 goto tail_call;
1139 }
1140 }
1141 }
1142
1143 next:
1144 ref = GC_STACK_POP();
1145 if (ref) {
1146 goto tail_call;
1147 }
1148 }
1149
1150 /* Two-Finger compaction algorithm */
gc_compact(void)1151 static void gc_compact(void)
1152 {
1153 if (GC_G(num_roots) + GC_FIRST_ROOT != GC_G(first_unused)) {
1154 if (GC_G(num_roots)) {
1155 gc_root_buffer *free = GC_IDX2PTR(GC_FIRST_ROOT);
1156 gc_root_buffer *scan = GC_IDX2PTR(GC_G(first_unused) - 1);
1157 gc_root_buffer *end = GC_IDX2PTR(GC_G(num_roots));
1158 uint32_t idx;
1159 zend_refcounted *p;
1160
1161 while (free < scan) {
1162 while (!GC_IS_UNUSED(free->ref)) {
1163 free++;
1164 }
1165 while (GC_IS_UNUSED(scan->ref)) {
1166 scan--;
1167 }
1168 if (scan > free) {
1169 p = scan->ref;
1170 free->ref = p;
1171 p = GC_GET_PTR(p);
1172 idx = gc_compress(GC_PTR2IDX(free));
1173 GC_REF_SET_INFO(p, idx | GC_REF_COLOR(p));
1174 free++;
1175 scan--;
1176 if (scan <= end) {
1177 break;
1178 }
1179 }
1180 }
1181 }
1182 GC_G(unused) = GC_INVALID;
1183 GC_G(first_unused) = GC_G(num_roots) + GC_FIRST_ROOT;
1184 }
1185 }
1186
gc_mark_roots(gc_stack * stack)1187 static void gc_mark_roots(gc_stack *stack)
1188 {
1189 gc_root_buffer *current, *last;
1190
1191 gc_compact();
1192
1193 current = GC_IDX2PTR(GC_FIRST_ROOT);
1194 last = GC_IDX2PTR(GC_G(first_unused));
1195 while (current != last) {
1196 if (GC_IS_ROOT(current->ref)) {
1197 if (GC_REF_CHECK_COLOR(current->ref, GC_PURPLE)) {
1198 GC_REF_SET_COLOR(current->ref, GC_GREY);
1199 gc_mark_grey(current->ref, stack);
1200 }
1201 }
1202 current++;
1203 }
1204 }
1205
gc_scan(zend_refcounted * ref,gc_stack * stack)1206 static void gc_scan(zend_refcounted *ref, gc_stack *stack)
1207 {
1208 HashTable *ht;
1209 Bucket *p;
1210 zval *zv;
1211 uint32_t n;
1212 GC_STACK_DCL(stack);
1213
1214 tail_call:
1215 if (!GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1216 goto next;
1217 }
1218
1219 if (GC_REFCOUNT(ref) > 0) {
1220 if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
1221 GC_REF_SET_BLACK(ref);
1222 if (UNEXPECTED(!_stack->next)) {
1223 gc_stack_next(_stack);
1224 }
1225 /* Split stack and reuse the tail */
1226 _stack->next->prev = NULL;
1227 gc_scan_black(ref, _stack->next);
1228 _stack->next->prev = _stack;
1229 }
1230 goto next;
1231 }
1232
1233 if (GC_TYPE(ref) == IS_OBJECT) {
1234 zend_object *obj = (zend_object*)ref;
1235 if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) {
1236 zval *table;
1237 int len;
1238
1239 if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) {
1240 zend_weakmap_get_object_entry_gc(obj, &table, &len);
1241 n = len;
1242 zv = table;
1243 for (; n != 0; n--) {
1244 ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
1245 zval *entry = (zval*) Z_PTR_P(zv);
1246 if (Z_OPT_REFCOUNTED_P(entry)) {
1247 ref = Z_COUNTED_P(entry);
1248 if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1249 GC_REF_SET_COLOR(ref, GC_WHITE);
1250 GC_STACK_PUSH(ref);
1251 }
1252 }
1253 zv++;
1254 }
1255 }
1256
1257 ht = obj->handlers->get_gc(obj, &table, &len);
1258 n = len;
1259 zv = table;
1260 if (UNEXPECTED(ht)) {
1261 if (GC_REF_CHECK_COLOR(ht, GC_GREY)) {
1262 GC_REF_SET_COLOR(ht, GC_WHITE);
1263 GC_STACK_PUSH((zend_refcounted *) ht);
1264 for (; n != 0; n--) {
1265 if (Z_REFCOUNTED_P(zv)) {
1266 ref = Z_COUNTED_P(zv);
1267 if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1268 GC_REF_SET_COLOR(ref, GC_WHITE);
1269 GC_STACK_PUSH(ref);
1270 }
1271 }
1272 zv++;
1273 }
1274 goto handle_ht;
1275 }
1276 }
1277
1278 handle_zvals:
1279 for (; n != 0; n--) {
1280 if (Z_REFCOUNTED_P(zv)) {
1281 ref = Z_COUNTED_P(zv);
1282 if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1283 GC_REF_SET_COLOR(ref, GC_WHITE);
1284 zv++;
1285 while (--n) {
1286 if (Z_REFCOUNTED_P(zv)) {
1287 zend_refcounted *ref = Z_COUNTED_P(zv);
1288 if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1289 GC_REF_SET_COLOR(ref, GC_WHITE);
1290 GC_STACK_PUSH(ref);
1291 }
1292 }
1293 zv++;
1294 }
1295 goto tail_call;
1296 }
1297 }
1298 zv++;
1299 }
1300 }
1301 } else if (GC_TYPE(ref) == IS_ARRAY) {
1302 ht = (HashTable *)ref;
1303 ZEND_ASSERT(ht != &EG(symbol_table));
1304
1305 handle_ht:
1306 n = ht->nNumUsed;
1307 if (HT_IS_PACKED(ht)) {
1308 zv = ht->arPacked;
1309 goto handle_zvals;
1310 }
1311
1312 p = ht->arData;
1313 for (; n != 0; n--) {
1314 zv = &p->val;
1315 if (Z_TYPE_P(zv) == IS_INDIRECT) {
1316 zv = Z_INDIRECT_P(zv);
1317 }
1318 if (Z_REFCOUNTED_P(zv)) {
1319 ref = Z_COUNTED_P(zv);
1320 if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1321 GC_REF_SET_COLOR(ref, GC_WHITE);
1322 p++;
1323 while (--n) {
1324 zv = &p->val;
1325 if (Z_TYPE_P(zv) == IS_INDIRECT) {
1326 zv = Z_INDIRECT_P(zv);
1327 }
1328 if (Z_REFCOUNTED_P(zv)) {
1329 zend_refcounted *ref = Z_COUNTED_P(zv);
1330 if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1331 GC_REF_SET_COLOR(ref, GC_WHITE);
1332 GC_STACK_PUSH(ref);
1333 }
1334 }
1335 p++;
1336 }
1337 goto tail_call;
1338 }
1339 }
1340 p++;
1341 }
1342 } else if (GC_TYPE(ref) == IS_REFERENCE) {
1343 if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
1344 ref = Z_COUNTED(((zend_reference*)ref)->val);
1345 if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
1346 GC_REF_SET_COLOR(ref, GC_WHITE);
1347 goto tail_call;
1348 }
1349 }
1350 }
1351
1352 next:
1353 ref = GC_STACK_POP();
1354 if (ref) {
1355 goto tail_call;
1356 }
1357 }
1358
gc_scan_roots(gc_stack * stack)1359 static void gc_scan_roots(gc_stack *stack)
1360 {
1361 uint32_t idx, end;
1362 gc_root_buffer *current;
1363
1364 /* Root buffer might be reallocated during gc_scan,
1365 * make sure to reload pointers. */
1366 idx = GC_FIRST_ROOT;
1367 end = GC_G(first_unused);
1368 while (idx != end) {
1369 current = GC_IDX2PTR(idx);
1370 if (GC_IS_ROOT(current->ref)) {
1371 if (GC_REF_CHECK_COLOR(current->ref, GC_GREY)) {
1372 GC_REF_SET_COLOR(current->ref, GC_WHITE);
1373 gc_scan(current->ref, stack);
1374 }
1375 }
1376 idx++;
1377 }
1378
1379 /* Scan extra roots added during gc_scan */
1380 while (idx != GC_G(first_unused)) {
1381 current = GC_IDX2PTR(idx);
1382 if (GC_IS_ROOT(current->ref)) {
1383 if (GC_REF_CHECK_COLOR(current->ref, GC_GREY)) {
1384 GC_REF_SET_COLOR(current->ref, GC_WHITE);
1385 gc_scan(current->ref, stack);
1386 }
1387 }
1388 idx++;
1389 }
1390 }
1391
gc_add_garbage(zend_refcounted * ref)1392 static void gc_add_garbage(zend_refcounted *ref)
1393 {
1394 uint32_t idx;
1395 gc_root_buffer *buf;
1396
1397 if (GC_HAS_UNUSED()) {
1398 idx = GC_FETCH_UNUSED();
1399 } else if (GC_HAS_NEXT_UNUSED()) {
1400 idx = GC_FETCH_NEXT_UNUSED();
1401 } else {
1402 gc_grow_root_buffer();
1403 if (UNEXPECTED(!GC_HAS_NEXT_UNUSED())) {
1404 return;
1405 }
1406 idx = GC_FETCH_NEXT_UNUSED();
1407 }
1408
1409 buf = GC_IDX2PTR(idx);
1410 buf->ref = GC_MAKE_GARBAGE(ref);
1411
1412 idx = gc_compress(idx);
1413 GC_REF_SET_INFO(ref, idx | GC_BLACK);
1414 GC_G(num_roots)++;
1415 }
1416
gc_collect_white(zend_refcounted * ref,uint32_t * flags,gc_stack * stack)1417 static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *stack)
1418 {
1419 int count = 0;
1420 HashTable *ht;
1421 Bucket *p;
1422 zval *zv;
1423 uint32_t n;
1424 GC_STACK_DCL(stack);
1425
1426 tail_call:
1427 /* don't count references for compatibility ??? */
1428 if (GC_TYPE(ref) != IS_REFERENCE) {
1429 count++;
1430 }
1431
1432 if (GC_TYPE(ref) == IS_OBJECT) {
1433 zend_object *obj = (zend_object*)ref;
1434
1435 if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) {
1436 int len;
1437 zval *table;
1438
1439 /* optimization: color is GC_BLACK (0) */
1440 if (!GC_INFO(ref)) {
1441 gc_add_garbage(ref);
1442 }
1443 if (!(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)
1444 && (obj->handlers->dtor_obj != zend_objects_destroy_object
1445 || obj->ce->destructor != NULL)) {
1446 *flags |= GC_HAS_DESTRUCTORS;
1447 }
1448
1449 if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) {
1450 zend_weakmap_get_object_entry_gc(obj, &table, &len);
1451 n = len;
1452 zv = table;
1453 for (; n != 0; n--) {
1454 ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
1455 zval *entry = (zval*) Z_PTR_P(zv);
1456 if (Z_REFCOUNTED_P(entry) && GC_FROM_WEAKMAP_KEY(entry)) {
1457 GC_UNSET_FROM_WEAKMAP_KEY(entry);
1458 GC_UNSET_FROM_WEAKMAP(entry);
1459 ref = Z_COUNTED_P(entry);
1460 GC_ADDREF(ref);
1461 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1462 GC_REF_SET_BLACK(ref);
1463 GC_STACK_PUSH(ref);
1464 }
1465 }
1466 zv++;
1467 }
1468 }
1469
1470 if (UNEXPECTED(obj->handlers->get_gc == zend_weakmap_get_gc)) {
1471 zend_weakmap_get_entry_gc(obj, &table, &len);
1472 n = len;
1473 zv = table;
1474 for (; n != 0; n--) {
1475 ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
1476 zval *entry = (zval*) Z_PTR_P(zv);
1477 if (Z_REFCOUNTED_P(entry) && GC_FROM_WEAKMAP(entry)) {
1478 GC_UNSET_FROM_WEAKMAP_KEY(entry);
1479 GC_UNSET_FROM_WEAKMAP(entry);
1480 ref = Z_COUNTED_P(entry);
1481 GC_ADDREF(ref);
1482 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1483 GC_REF_SET_BLACK(ref);
1484 GC_STACK_PUSH(ref);
1485 }
1486 }
1487 zv++;
1488 }
1489 goto next;
1490 }
1491
1492 ht = obj->handlers->get_gc(obj, &table, &len);
1493 n = len;
1494 zv = table;
1495 if (UNEXPECTED(ht)) {
1496 GC_ADDREF(ht);
1497 if (GC_REF_CHECK_COLOR(ht, GC_WHITE)) {
1498 GC_REF_SET_BLACK(ht);
1499 for (; n != 0; n--) {
1500 if (Z_REFCOUNTED_P(zv)) {
1501 ref = Z_COUNTED_P(zv);
1502 GC_ADDREF(ref);
1503 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1504 GC_REF_SET_BLACK(ref);
1505 GC_STACK_PUSH(ref);
1506 }
1507 }
1508 zv++;
1509 }
1510 goto handle_ht;
1511 }
1512 }
1513
1514 handle_zvals:
1515 for (; n != 0; n--) {
1516 if (Z_REFCOUNTED_P(zv)) {
1517 ref = Z_COUNTED_P(zv);
1518 GC_ADDREF(ref);
1519 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1520 GC_REF_SET_BLACK(ref);
1521 zv++;
1522 while (--n) {
1523 if (Z_REFCOUNTED_P(zv)) {
1524 zend_refcounted *ref = Z_COUNTED_P(zv);
1525 GC_ADDREF(ref);
1526 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1527 GC_REF_SET_BLACK(ref);
1528 GC_STACK_PUSH(ref);
1529 }
1530 }
1531 zv++;
1532 }
1533 goto tail_call;
1534 }
1535 }
1536 zv++;
1537 }
1538 }
1539 } else if (GC_TYPE(ref) == IS_ARRAY) {
1540 /* optimization: color is GC_BLACK (0) */
1541 if (!GC_INFO(ref)) {
1542 gc_add_garbage(ref);
1543 }
1544 ht = (zend_array*)ref;
1545
1546 handle_ht:
1547 n = ht->nNumUsed;
1548 if (HT_IS_PACKED(ht)) {
1549 zv = ht->arPacked;
1550 goto handle_zvals;
1551 }
1552
1553 p = ht->arData;
1554 for (; n != 0; n--) {
1555 zv = &p->val;
1556 if (Z_TYPE_P(zv) == IS_INDIRECT) {
1557 zv = Z_INDIRECT_P(zv);
1558 }
1559 if (Z_REFCOUNTED_P(zv)) {
1560 ref = Z_COUNTED_P(zv);
1561 GC_ADDREF(ref);
1562 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1563 GC_REF_SET_BLACK(ref);
1564 p++;
1565 while (--n) {
1566 zv = &p->val;
1567 if (Z_TYPE_P(zv) == IS_INDIRECT) {
1568 zv = Z_INDIRECT_P(zv);
1569 }
1570 if (Z_REFCOUNTED_P(zv)) {
1571 zend_refcounted *ref = Z_COUNTED_P(zv);
1572 GC_ADDREF(ref);
1573 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1574 GC_REF_SET_BLACK(ref);
1575 GC_STACK_PUSH(ref);
1576 }
1577 }
1578 p++;
1579 }
1580 goto tail_call;
1581 }
1582 }
1583 p++;
1584 }
1585 } else if (GC_TYPE(ref) == IS_REFERENCE) {
1586 if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
1587 ref = Z_COUNTED(((zend_reference*)ref)->val);
1588 GC_ADDREF(ref);
1589 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1590 GC_REF_SET_BLACK(ref);
1591 goto tail_call;
1592 }
1593 }
1594 }
1595
1596 next:
1597 ref = GC_STACK_POP();
1598 if (ref) {
1599 goto tail_call;
1600 }
1601
1602 return count;
1603 }
1604
gc_collect_roots(uint32_t * flags,gc_stack * stack)1605 static int gc_collect_roots(uint32_t *flags, gc_stack *stack)
1606 {
1607 uint32_t idx, end;
1608 zend_refcounted *ref;
1609 int count = 0;
1610 gc_root_buffer *current = GC_IDX2PTR(GC_FIRST_ROOT);
1611 gc_root_buffer *last = GC_IDX2PTR(GC_G(first_unused));
1612
1613 /* remove non-garbage from the list */
1614 while (current != last) {
1615 if (GC_IS_ROOT(current->ref)) {
1616 if (GC_REF_CHECK_COLOR(current->ref, GC_BLACK)) {
1617 GC_REF_SET_INFO(current->ref, 0); /* reset GC_ADDRESS() and keep GC_BLACK */
1618 gc_remove_from_roots(current);
1619 }
1620 }
1621 current++;
1622 }
1623
1624 gc_compact();
1625
1626 /* Root buffer might be reallocated during gc_collect_white,
1627 * make sure to reload pointers. */
1628 idx = GC_FIRST_ROOT;
1629 end = GC_G(first_unused);
1630 while (idx != end) {
1631 current = GC_IDX2PTR(idx);
1632 ref = current->ref;
1633 ZEND_ASSERT(GC_IS_ROOT(ref));
1634 current->ref = GC_MAKE_GARBAGE(ref);
1635 if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
1636 GC_REF_SET_BLACK(ref);
1637 count += gc_collect_white(ref, flags, stack);
1638 }
1639 idx++;
1640 }
1641
1642 return count;
1643 }
1644
gc_remove_nested_data_from_buffer(zend_refcounted * ref,gc_root_buffer * root,gc_stack * stack)1645 static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root, gc_stack *stack)
1646 {
1647 HashTable *ht;
1648 Bucket *p;
1649 zval *zv;
1650 uint32_t n;
1651 int count = 0;
1652 GC_STACK_DCL(stack);
1653
1654 tail_call:
1655 if (root) {
1656 root = NULL;
1657 count++;
1658 } else if (GC_REF_ADDRESS(ref) != 0
1659 && GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
1660 GC_TRACE_REF(ref, "removing from buffer");
1661 GC_REMOVE_FROM_BUFFER(ref);
1662 count++;
1663 } else if (GC_TYPE(ref) == IS_REFERENCE) {
1664 if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
1665 ref = Z_COUNTED(((zend_reference*)ref)->val);
1666 goto tail_call;
1667 }
1668 goto next;
1669 } else {
1670 goto next;
1671 }
1672
1673 if (GC_TYPE(ref) == IS_OBJECT) {
1674 zend_object *obj = (zend_object*)ref;
1675
1676 if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) {
1677 int len;
1678 zval *table;
1679
1680 if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) {
1681 zend_weakmap_get_object_entry_gc(obj, &table, &len);
1682 n = len;
1683 zv = table;
1684 for (; n != 0; n--) {
1685 ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
1686 zval *entry = (zval*) Z_PTR_P(zv);
1687 if (Z_OPT_REFCOUNTED_P(entry)) {
1688 ref = Z_COUNTED_P(entry);
1689 GC_STACK_PUSH(ref);
1690 }
1691 zv++;
1692 }
1693 }
1694
1695 ht = obj->handlers->get_gc(obj, &table, &len);
1696 n = len;
1697 zv = table;
1698 if (UNEXPECTED(ht)) {
1699 for (; n != 0; n--) {
1700 if (Z_REFCOUNTED_P(zv)) {
1701 ref = Z_COUNTED_P(zv);
1702 GC_STACK_PUSH(ref);
1703 }
1704 zv++;
1705 }
1706 if (GC_REF_ADDRESS(ht) != 0 && GC_REF_CHECK_COLOR(ht, GC_BLACK)) {
1707 GC_TRACE_REF(ht, "removing from buffer");
1708 GC_REMOVE_FROM_BUFFER(ht);
1709 }
1710 goto handle_ht;
1711 }
1712
1713 handle_zvals:
1714 for (; n != 0; n--) {
1715 if (Z_REFCOUNTED_P(zv)) {
1716 ref = Z_COUNTED_P(zv);
1717 zv++;
1718 while (--n) {
1719 if (Z_REFCOUNTED_P(zv)) {
1720 zend_refcounted *ref = Z_COUNTED_P(zv);
1721 GC_STACK_PUSH(ref);
1722 }
1723 zv++;
1724 }
1725 goto tail_call;
1726 }
1727 zv++;
1728 }
1729 }
1730 } else if (GC_TYPE(ref) == IS_ARRAY) {
1731 ht = (zend_array*)ref;
1732
1733 handle_ht:
1734 n = ht->nNumUsed;
1735 if (HT_IS_PACKED(ht)) {
1736 zv = ht->arPacked;
1737 goto handle_zvals;
1738 }
1739
1740 p = ht->arData;
1741 for (; n != 0; n--) {
1742 zv = &p->val;
1743 if (Z_TYPE_P(zv) == IS_INDIRECT) {
1744 zv = Z_INDIRECT_P(zv);
1745 }
1746 if (Z_REFCOUNTED_P(zv)) {
1747 ref = Z_COUNTED_P(zv);
1748 p++;
1749 while (--n) {
1750 zv = &p->val;
1751 if (Z_TYPE_P(zv) == IS_INDIRECT) {
1752 zv = Z_INDIRECT_P(zv);
1753 }
1754 if (Z_REFCOUNTED_P(zv)) {
1755 zend_refcounted *ref = Z_COUNTED_P(zv);
1756 GC_STACK_PUSH(ref);
1757 }
1758 p++;
1759 }
1760 goto tail_call;
1761 }
1762 p++;
1763 }
1764 }
1765
1766 next:
1767 ref = GC_STACK_POP();
1768 if (ref) {
1769 goto tail_call;
1770 }
1771
1772 return count;
1773 }
1774
1775 static void zend_get_gc_buffer_release(void);
1776 static void zend_gc_check_root_tmpvars(void);
1777 static void zend_gc_remove_root_tmpvars(void);
1778
zend_gc_collect_cycles(void)1779 ZEND_API int zend_gc_collect_cycles(void)
1780 {
1781 int total_count = 0;
1782 bool should_rerun_gc = 0;
1783 bool did_rerun_gc = 0;
1784
1785 zend_hrtime_t start_time = zend_hrtime();
1786 if (GC_G(num_roots) && GC_G(gc_active)) {
1787 zend_gc_remove_root_tmpvars();
1788 }
1789
1790 rerun_gc:
1791 if (GC_G(num_roots)) {
1792 int count;
1793 gc_root_buffer *current, *last;
1794 zend_refcounted *p;
1795 uint32_t gc_flags = 0;
1796 uint32_t idx, end;
1797 gc_stack stack;
1798
1799 stack.prev = NULL;
1800 stack.next = NULL;
1801
1802 if (GC_G(gc_active)) {
1803 GC_G(collector_time) += zend_hrtime() - start_time;
1804 return 0;
1805 }
1806
1807 GC_TRACE("Collecting cycles");
1808 GC_G(gc_runs)++;
1809 GC_G(gc_active) = 1;
1810
1811 GC_TRACE("Marking roots");
1812 gc_mark_roots(&stack);
1813 GC_TRACE("Scanning roots");
1814 gc_scan_roots(&stack);
1815
1816 GC_TRACE("Collecting roots");
1817 count = gc_collect_roots(&gc_flags, &stack);
1818
1819 if (!GC_G(num_roots)) {
1820 /* nothing to free */
1821 GC_TRACE("Nothing to free");
1822 gc_stack_free(&stack);
1823 GC_G(gc_active) = 0;
1824 goto finish;
1825 }
1826
1827 zend_fiber_switch_block();
1828
1829 end = GC_G(first_unused);
1830
1831 if (gc_flags & GC_HAS_DESTRUCTORS) {
1832 GC_TRACE("Calling destructors");
1833
1834 /* During a destructor call, new externally visible references to nested data may
1835 * be introduced. These references can be introduced in a way that does not
1836 * modify any refcounts, so we have no real way to detect this situation
1837 * short of rerunning full GC tracing. What we do instead is to only run
1838 * destructors at this point and automatically re-run GC afterwards. */
1839 should_rerun_gc = 1;
1840
1841 /* Mark all roots for which a dtor will be invoked as DTOR_GARBAGE. Additionally
1842 * color them purple. This serves a double purpose: First, they should be
1843 * considered new potential roots for the next GC run. Second, it will prevent
1844 * their removal from the root buffer by nested data removal. */
1845 idx = GC_FIRST_ROOT;
1846 current = GC_IDX2PTR(GC_FIRST_ROOT);
1847 while (idx != end) {
1848 if (GC_IS_GARBAGE(current->ref)) {
1849 p = GC_GET_PTR(current->ref);
1850 if (GC_TYPE(p) == IS_OBJECT && !(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
1851 zend_object *obj = (zend_object *) p;
1852 if (obj->handlers->dtor_obj != zend_objects_destroy_object
1853 || obj->ce->destructor) {
1854 current->ref = GC_MAKE_DTOR_GARBAGE(obj);
1855 GC_REF_SET_COLOR(obj, GC_PURPLE);
1856 } else {
1857 GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
1858 }
1859 }
1860 }
1861 current++;
1862 idx++;
1863 }
1864
1865 /* Remove nested data for objects on which a destructor will be called.
1866 * This will not remove the objects themselves, as they have been colored
1867 * purple. */
1868 idx = GC_FIRST_ROOT;
1869 current = GC_IDX2PTR(GC_FIRST_ROOT);
1870 while (idx != end) {
1871 if (GC_IS_DTOR_GARBAGE(current->ref)) {
1872 p = GC_GET_PTR(current->ref);
1873 count -= gc_remove_nested_data_from_buffer(p, current, &stack);
1874 }
1875 current++;
1876 idx++;
1877 }
1878
1879 /* Actually call destructors.
1880 *
1881 * The root buffer might be reallocated during destructors calls,
1882 * make sure to reload pointers as necessary. */
1883 zend_hrtime_t dtor_start_time = zend_hrtime();
1884 idx = GC_FIRST_ROOT;
1885 while (idx != end) {
1886 current = GC_IDX2PTR(idx);
1887 if (GC_IS_DTOR_GARBAGE(current->ref)) {
1888 p = GC_GET_PTR(current->ref);
1889 /* Mark this is as a normal root for the next GC run,
1890 * it's no longer garbage for this run. */
1891 current->ref = p;
1892 /* Double check that the destructor hasn't been called yet. It could have
1893 * already been invoked indirectly by some other destructor. */
1894 if (!(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
1895 zend_object *obj = (zend_object*)p;
1896 GC_TRACE_REF(obj, "calling destructor");
1897 GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
1898 GC_ADDREF(obj);
1899 obj->handlers->dtor_obj(obj);
1900 GC_DELREF(obj);
1901 }
1902 }
1903 idx++;
1904 }
1905 GC_G(dtor_time) += zend_hrtime() - dtor_start_time;
1906
1907 if (GC_G(gc_protected)) {
1908 /* something went wrong */
1909 zend_get_gc_buffer_release();
1910 zend_fiber_switch_unblock();
1911 GC_G(collector_time) += zend_hrtime() - start_time;
1912 return 0;
1913 }
1914 }
1915
1916 gc_stack_free(&stack);
1917
1918 /* Destroy zvals. The root buffer may be reallocated. */
1919 GC_TRACE("Destroying zvals");
1920 zend_hrtime_t free_start_time = zend_hrtime();
1921 idx = GC_FIRST_ROOT;
1922 while (idx != end) {
1923 current = GC_IDX2PTR(idx);
1924 if (GC_IS_GARBAGE(current->ref)) {
1925 p = GC_GET_PTR(current->ref);
1926 GC_TRACE_REF(p, "destroying");
1927 if (GC_TYPE(p) == IS_OBJECT) {
1928 zend_object *obj = (zend_object*)p;
1929
1930 EG(objects_store).object_buckets[obj->handle] = SET_OBJ_INVALID(obj);
1931 GC_TYPE_INFO(obj) = GC_NULL |
1932 (GC_TYPE_INFO(obj) & ~GC_TYPE_MASK);
1933 /* Modify current before calling free_obj (bug #78811: free_obj() can cause the root buffer (with current) to be reallocated.) */
1934 current->ref = GC_MAKE_GARBAGE(((char*)obj) - obj->handlers->offset);
1935 if (!(OBJ_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {
1936 GC_ADD_FLAGS(obj, IS_OBJ_FREE_CALLED);
1937 GC_ADDREF(obj);
1938 obj->handlers->free_obj(obj);
1939 GC_DELREF(obj);
1940 }
1941
1942 ZEND_OBJECTS_STORE_ADD_TO_FREE_LIST(obj->handle);
1943 } else if (GC_TYPE(p) == IS_ARRAY) {
1944 zend_array *arr = (zend_array*)p;
1945
1946 GC_TYPE_INFO(arr) = GC_NULL |
1947 (GC_TYPE_INFO(arr) & ~GC_TYPE_MASK);
1948
1949 /* GC may destroy arrays with rc>1. This is valid and safe. */
1950 HT_ALLOW_COW_VIOLATION(arr);
1951
1952 zend_hash_destroy(arr);
1953 }
1954 }
1955 idx++;
1956 }
1957
1958 /* Free objects */
1959 current = GC_IDX2PTR(GC_FIRST_ROOT);
1960 last = GC_IDX2PTR(end);
1961 while (current != last) {
1962 if (GC_IS_GARBAGE(current->ref)) {
1963 p = GC_GET_PTR(current->ref);
1964 GC_LINK_UNUSED(current);
1965 GC_G(num_roots)--;
1966 efree(p);
1967 }
1968 current++;
1969 }
1970
1971 GC_G(free_time) += zend_hrtime() - free_start_time;
1972
1973 zend_fiber_switch_unblock();
1974
1975 GC_TRACE("Collection finished");
1976 GC_G(collected) += count;
1977 total_count += count;
1978 GC_G(gc_active) = 0;
1979 }
1980
1981 gc_compact();
1982
1983 /* Objects with destructors were removed from this GC run. Rerun GC right away to clean them
1984 * up. We do this only once: If we encounter more destructors on the second run, we'll not
1985 * run GC another time. */
1986 if (should_rerun_gc && !did_rerun_gc) {
1987 did_rerun_gc = 1;
1988 goto rerun_gc;
1989 }
1990
1991 finish:
1992 zend_get_gc_buffer_release();
1993
1994 /* Prevent GC from running during zend_gc_check_root_tmpvars, before
1995 * gc_threshold is adjusted, as this may result in unbounded recursion */
1996 GC_G(gc_active) = 1;
1997 zend_gc_check_root_tmpvars();
1998 GC_G(gc_active) = 0;
1999
2000 GC_G(collector_time) += zend_hrtime() - start_time;
2001 return total_count;
2002 }
2003
zend_gc_get_status(zend_gc_status * status)2004 ZEND_API void zend_gc_get_status(zend_gc_status *status)
2005 {
2006 status->active = GC_G(gc_active);
2007 status->gc_protected = GC_G(gc_protected);
2008 status->full = GC_G(gc_full);
2009 status->runs = GC_G(gc_runs);
2010 status->collected = GC_G(collected);
2011 status->threshold = GC_G(gc_threshold);
2012 status->buf_size = GC_G(buf_size);
2013 status->num_roots = GC_G(num_roots);
2014 status->application_time = zend_hrtime() - GC_G(activated_at);
2015 status->collector_time = GC_G(collector_time);
2016 status->dtor_time = GC_G(dtor_time);
2017 status->free_time = GC_G(free_time);
2018 }
2019
zend_get_gc_buffer_create(void)2020 ZEND_API zend_get_gc_buffer *zend_get_gc_buffer_create(void) {
2021 /* There can only be one get_gc() call active at a time,
2022 * so there only needs to be one buffer. */
2023 zend_get_gc_buffer *gc_buffer = &EG(get_gc_buffer);
2024 gc_buffer->cur = gc_buffer->start;
2025 return gc_buffer;
2026 }
2027
zend_get_gc_buffer_grow(zend_get_gc_buffer * gc_buffer)2028 ZEND_API void zend_get_gc_buffer_grow(zend_get_gc_buffer *gc_buffer) {
2029 size_t old_capacity = gc_buffer->end - gc_buffer->start;
2030 size_t new_capacity = old_capacity == 0 ? 64 : old_capacity * 2;
2031 gc_buffer->start = erealloc(gc_buffer->start, new_capacity * sizeof(zval));
2032 gc_buffer->end = gc_buffer->start + new_capacity;
2033 gc_buffer->cur = gc_buffer->start + old_capacity;
2034 }
2035
zend_get_gc_buffer_release(void)2036 static void zend_get_gc_buffer_release(void) {
2037 zend_get_gc_buffer *gc_buffer = &EG(get_gc_buffer);
2038 efree(gc_buffer->start);
2039 gc_buffer->start = gc_buffer->end = gc_buffer->cur = NULL;
2040 }
2041
2042 /* TMPVAR operands are destroyed using zval_ptr_dtor_nogc(), because they usually cannot contain
2043 * cycles. However, there are some rare exceptions where this is possible, in which case we rely
2044 * on the producing code to root the value. If a GC run occurs between the rooting and consumption
2045 * of the value, we would end up leaking it. To avoid this, root all live TMPVAR values here. */
zend_gc_check_root_tmpvars(void)2046 static void zend_gc_check_root_tmpvars(void) {
2047 zend_execute_data *ex = EG(current_execute_data);
2048 for (; ex; ex = ex->prev_execute_data) {
2049 zend_function *func = ex->func;
2050 if (!func || !ZEND_USER_CODE(func->type)) {
2051 continue;
2052 }
2053
2054 uint32_t op_num = ex->opline - ex->func->op_array.opcodes;
2055 for (uint32_t i = 0; i < func->op_array.last_live_range; i++) {
2056 const zend_live_range *range = &func->op_array.live_range[i];
2057 if (range->start > op_num) {
2058 break;
2059 }
2060 if (range->end <= op_num) {
2061 continue;
2062 }
2063
2064 uint32_t kind = range->var & ZEND_LIVE_MASK;
2065 if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) {
2066 uint32_t var_num = range->var & ~ZEND_LIVE_MASK;
2067 zval *var = ZEND_CALL_VAR(ex, var_num);
2068 if (Z_REFCOUNTED_P(var)) {
2069 gc_check_possible_root(Z_COUNTED_P(var));
2070 }
2071 }
2072 }
2073 }
2074 }
2075
zend_gc_remove_root_tmpvars(void)2076 static void zend_gc_remove_root_tmpvars(void) {
2077 zend_execute_data *ex = EG(current_execute_data);
2078 for (; ex; ex = ex->prev_execute_data) {
2079 zend_function *func = ex->func;
2080 if (!func || !ZEND_USER_CODE(func->type)) {
2081 continue;
2082 }
2083
2084 uint32_t op_num = ex->opline - ex->func->op_array.opcodes;
2085 for (uint32_t i = 0; i < func->op_array.last_live_range; i++) {
2086 const zend_live_range *range = &func->op_array.live_range[i];
2087 if (range->start > op_num) {
2088 break;
2089 }
2090 if (range->end <= op_num) {
2091 continue;
2092 }
2093
2094 uint32_t kind = range->var & ZEND_LIVE_MASK;
2095 if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) {
2096 uint32_t var_num = range->var & ~ZEND_LIVE_MASK;
2097 zval *var = ZEND_CALL_VAR(ex, var_num);
2098 if (Z_REFCOUNTED_P(var)) {
2099 GC_REMOVE_FROM_BUFFER(Z_COUNTED_P(var));
2100 }
2101 }
2102 }
2103 }
2104 }
2105
2106 #if GC_BENCH
gc_bench_print(void)2107 void gc_bench_print(void)
2108 {
2109 fprintf(stderr, "GC Statistics\n");
2110 fprintf(stderr, "-------------\n");
2111 fprintf(stderr, "Runs: %d\n", GC_G(gc_runs));
2112 fprintf(stderr, "Collected: %d\n", GC_G(collected));
2113 fprintf(stderr, "Root buffer length: %d\n", GC_G(root_buf_length));
2114 fprintf(stderr, "Root buffer peak: %d\n\n", GC_G(root_buf_peak));
2115 fprintf(stderr, " Possible Remove from Marked\n");
2116 fprintf(stderr, " Root Buffered buffer grey\n");
2117 fprintf(stderr, " -------- -------- ----------- ------\n");
2118 fprintf(stderr, "ZVAL %8d %8d %9d %8d\n", GC_G(zval_possible_root), GC_G(zval_buffered), GC_G(zval_remove_from_buffer), GC_G(zval_marked_grey));
2119 }
2120 #endif
2121
2122 #ifdef ZTS
zend_gc_globals_size(void)2123 size_t zend_gc_globals_size(void)
2124 {
2125 return sizeof(zend_gc_globals);
2126 }
2127 #endif
2128