xref: /PHP-7.1/Zend/zend_gc.c (revision ccd4716e)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend Engine                                                          |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1998-2018 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@zend.com>                             |
17    +----------------------------------------------------------------------+
18 */
19 
20 /* $Id$ */
21 
22 
23 /**
24  * zend_gc_collect_cycles
25  * ======================
26  *
27  * Colors and its meaning
28  * ----------------------
29  *
30  * BLACK  (GC_BLACK)   - In use or free.
31  * GREY   (GC_GREY)    - Possible member of cycle.
32  * WHITE  (GC_WHITE)   - Member of garbage cycle.
33  * PURPLE (GC_PURPLE)  - Possible root of cycle.
34  *
35  * Colors described in the paper but not used
36  * ------------------------------------------
37  *
38  * GREEN - Acyclic
39  * RED   - Candidate cycle underogin
40  * ORANGE - Candidate cycle awaiting epoch boundary.
41  *
42  *
43  * Flow
44  * =====
45  *
46  * The garbage collect cycle starts from 'gc_mark_roots', which traverses the
47  * possible roots, and calls mark_grey for roots are marked purple with
48  * depth-first traverse.
49  *
50  * After all possible roots are traversed and marked,
51  * gc_scan_roots will be called, and each root will be called with
52  * gc_scan(root->ref)
53  *
54  * gc_scan checkes the colors of possible members.
55  *
56  * If the node is marked as grey and the refcount > 0
57  *    gc_scan_black will be called on that node to scan it's subgraph.
58  * otherwise (refcount == 0), it marks the node white.
59  *
60  * A node MAY be added to possbile roots when ZEND_UNSET_VAR happens or
61  * zend_assign_to_variable is called only when possible garbage node is
62  * produced.
63  * gc_possible_root() will be called to add the nodes to possible roots.
64  *
65  *
66  * For objects, we call their get_gc handler (by default 'zend_std_get_gc') to
67  * get the object properties to scan.
68  *
69  *
70  * @see http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
71  */
72 #include "zend.h"
73 #include "zend_API.h"
74 
75 /* one (0) is reserved */
76 #define GC_ROOT_BUFFER_MAX_ENTRIES 10001
77 
78 #define GC_HAS_DESTRUCTORS  (1<<0)
79 
80 #ifndef ZEND_GC_DEBUG
81 # define ZEND_GC_DEBUG 0
82 #endif
83 
84 #ifdef ZTS
85 ZEND_API int gc_globals_id;
86 #else
87 ZEND_API zend_gc_globals gc_globals;
88 #endif
89 
90 ZEND_API int (*gc_collect_cycles)(void);
91 
92 #if ZEND_GC_DEBUG > 1
93 # define GC_TRACE(format, ...) fprintf(stderr, format "\n", ##__VA_ARGS__);
94 # define GC_TRACE_REF(ref, format, ...) \
95 	do { \
96 		gc_trace_ref((zend_refcounted *) ref); \
97 		fprintf(stderr, format "\n", ##__VA_ARGS__); \
98 	} while (0)
99 # define GC_TRACE_SET_COLOR(ref, color) \
100 	GC_TRACE_REF(ref, "->%s", gc_color_name(color))
101 #else
102 # define GC_TRACE_REF(ref, format, ...)
103 # define GC_TRACE_SET_COLOR(ref, new_color)
104 # define GC_TRACE(str)
105 #endif
106 
107 #define GC_REF_SET_ADDRESS(ref, a) \
108 	GC_INFO_SET_ADDRESS(GC_INFO(ref), a)
109 #define GC_REF_GET_COLOR(ref) \
110 	GC_INFO_GET_COLOR(GC_INFO(ref))
111 #define GC_REF_SET_COLOR(ref, c) \
112 	do { GC_TRACE_SET_COLOR(ref, c); GC_INFO_SET_COLOR(GC_INFO(ref), c); } while (0)
113 #define GC_REF_SET_BLACK(ref) \
114 	do { GC_TRACE_SET_COLOR(ref, GC_BLACK); GC_INFO_SET_BLACK(GC_INFO(ref)); } while (0)
115 #define GC_REF_SET_PURPLE(ref) \
116 	do { GC_TRACE_SET_COLOR(ref, GC_PURPLE); GC_INFO_SET_PURPLE(GC_INFO(ref)); } while (0)
117 
118 #if ZEND_GC_DEBUG > 1
gc_color_name(uint32_t color)119 static const char *gc_color_name(uint32_t color) {
120 	switch (color) {
121 		case GC_BLACK: return "black";
122 		case GC_WHITE: return "white";
123 		case GC_GREY: return "grey";
124 		case GC_PURPLE: return "purple";
125 		default: return "unknown";
126 	}
127 }
gc_trace_ref(zend_refcounted * ref)128 static void gc_trace_ref(zend_refcounted *ref) {
129 	if (GC_TYPE(ref) == IS_OBJECT) {
130 		zend_object *obj = (zend_object *) ref;
131 		fprintf(stderr, "[%p] rc=%d addr=%d %s object(%s)#%d ",
132 			ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
133 			gc_color_name(GC_REF_GET_COLOR(ref)),
134 			obj->ce->name->val, obj->handle);
135 	} else if (GC_TYPE(ref) == IS_ARRAY) {
136 		zend_array *arr = (zend_array *) ref;
137 		fprintf(stderr, "[%p] rc=%d addr=%d %s array(%d) ",
138 			ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
139 			gc_color_name(GC_REF_GET_COLOR(ref)),
140 			zend_hash_num_elements(arr));
141 	} else {
142 		fprintf(stderr, "[%p] rc=%d addr=%d %s %s ",
143 			ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
144 			gc_color_name(GC_REF_GET_COLOR(ref)),
145 			zend_get_type_by_const(GC_TYPE(ref)));
146 	}
147 }
148 #endif
149 
gc_remove_from_roots(gc_root_buffer * root)150 static zend_always_inline void gc_remove_from_roots(gc_root_buffer *root)
151 {
152 	root->next->prev = root->prev;
153 	root->prev->next = root->next;
154 	root->prev = GC_G(unused);
155 	GC_G(unused) = root;
156 	GC_BENCH_DEC(root_buf_length);
157 }
158 
gc_remove_from_additional_roots(gc_root_buffer * root)159 static zend_always_inline void gc_remove_from_additional_roots(gc_root_buffer *root)
160 {
161 	root->next->prev = root->prev;
162 	root->prev->next = root->next;
163 }
164 
root_buffer_dtor(zend_gc_globals * gc_globals)165 static void root_buffer_dtor(zend_gc_globals *gc_globals)
166 {
167 	if (gc_globals->buf) {
168 		free(gc_globals->buf);
169 		gc_globals->buf = NULL;
170 	}
171 }
172 
gc_globals_ctor_ex(zend_gc_globals * gc_globals)173 static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
174 {
175 	gc_globals->gc_enabled = 0;
176 	gc_globals->gc_active = 0;
177 
178 	gc_globals->buf = NULL;
179 
180 	gc_globals->roots.next = &gc_globals->roots;
181 	gc_globals->roots.prev = &gc_globals->roots;
182 	gc_globals->unused = NULL;
183 	gc_globals->next_to_free = NULL;
184 
185 	gc_globals->to_free.next = &gc_globals->to_free;
186 	gc_globals->to_free.prev = &gc_globals->to_free;
187 
188 	gc_globals->gc_runs = 0;
189 	gc_globals->collected = 0;
190 
191 	gc_globals->additional_buffer = NULL;
192 
193 #if GC_BENCH
194 	gc_globals->root_buf_length = 0;
195 	gc_globals->root_buf_peak = 0;
196 	gc_globals->zval_possible_root = 0;
197 	gc_globals->zval_buffered = 0;
198 	gc_globals->zval_remove_from_buffer = 0;
199 	gc_globals->zval_marked_grey = 0;
200 #endif
201 }
202 
gc_globals_ctor(void)203 ZEND_API void gc_globals_ctor(void)
204 {
205 #ifdef ZTS
206 	ts_allocate_id(&gc_globals_id, sizeof(zend_gc_globals), (ts_allocate_ctor) gc_globals_ctor_ex, (ts_allocate_dtor) root_buffer_dtor);
207 #else
208 	gc_globals_ctor_ex(&gc_globals);
209 #endif
210 }
211 
gc_globals_dtor(void)212 ZEND_API void gc_globals_dtor(void)
213 {
214 #ifndef ZTS
215 	root_buffer_dtor(&gc_globals);
216 #endif
217 }
218 
gc_reset(void)219 ZEND_API void gc_reset(void)
220 {
221 	GC_G(gc_runs) = 0;
222 	GC_G(collected) = 0;
223 	GC_G(gc_full) = 0;
224 
225 #if GC_BENCH
226 	GC_G(root_buf_length) = 0;
227 	GC_G(root_buf_peak) = 0;
228 	GC_G(zval_possible_root) = 0;
229 	GC_G(zval_buffered) = 0;
230 	GC_G(zval_remove_from_buffer) = 0;
231 	GC_G(zval_marked_grey) = 0;
232 #endif
233 
234 	GC_G(roots).next = &GC_G(roots);
235 	GC_G(roots).prev = &GC_G(roots);
236 
237 	GC_G(to_free).next = &GC_G(to_free);
238 	GC_G(to_free).prev = &GC_G(to_free);
239 
240 	if (GC_G(buf)) {
241 		GC_G(unused) = NULL;
242 		GC_G(first_unused) = GC_G(buf) + 1;
243 	} else {
244 		GC_G(unused) = NULL;
245 		GC_G(first_unused) = NULL;
246 		GC_G(last_unused) = NULL;
247 	}
248 
249 	GC_G(additional_buffer) = NULL;
250 }
251 
gc_init(void)252 ZEND_API void gc_init(void)
253 {
254 	if (GC_G(buf) == NULL && GC_G(gc_enabled)) {
255 		GC_G(buf) = (gc_root_buffer*) malloc(sizeof(gc_root_buffer) * GC_ROOT_BUFFER_MAX_ENTRIES);
256 		GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
257 		gc_reset();
258 	}
259 }
260 
gc_possible_root(zend_refcounted * ref)261 ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
262 {
263 	gc_root_buffer *newRoot;
264 
265 	if (UNEXPECTED(CG(unclean_shutdown)) || UNEXPECTED(GC_G(gc_active))) {
266 		return;
267 	}
268 
269 	ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT);
270 	ZEND_ASSERT(EXPECTED(GC_REF_GET_COLOR(ref) == GC_BLACK));
271 	ZEND_ASSERT(!GC_ADDRESS(GC_INFO(ref)));
272 
273 	GC_BENCH_INC(zval_possible_root);
274 
275 	newRoot = GC_G(unused);
276 	if (newRoot) {
277 		GC_G(unused) = newRoot->prev;
278 	} else if (GC_G(first_unused) != GC_G(last_unused)) {
279 		newRoot = GC_G(first_unused);
280 		GC_G(first_unused)++;
281 	} else {
282 		if (!GC_G(gc_enabled)) {
283 			return;
284 		}
285 		GC_REFCOUNT(ref)++;
286 		gc_collect_cycles();
287 		GC_REFCOUNT(ref)--;
288 		if (UNEXPECTED(GC_REFCOUNT(ref)) == 0) {
289 			zval_dtor_func(ref);
290 			return;
291 		}
292 		if (UNEXPECTED(GC_INFO(ref))) {
293 			return;
294 		}
295 		newRoot = GC_G(unused);
296 		if (!newRoot) {
297 #if ZEND_GC_DEBUG
298 			if (!GC_G(gc_full)) {
299 				fprintf(stderr, "GC: no space to record new root candidate\n");
300 				GC_G(gc_full) = 1;
301 			}
302 #endif
303 			return;
304 		}
305 		GC_G(unused) = newRoot->prev;
306 	}
307 
308 	GC_TRACE_SET_COLOR(ref, GC_PURPLE);
309 	GC_INFO(ref) = (newRoot - GC_G(buf)) | GC_PURPLE;
310 	newRoot->ref = ref;
311 
312 	newRoot->next = GC_G(roots).next;
313 	newRoot->prev = &GC_G(roots);
314 	GC_G(roots).next->prev = newRoot;
315 	GC_G(roots).next = newRoot;
316 
317 	GC_BENCH_INC(zval_buffered);
318 	GC_BENCH_INC(root_buf_length);
319 	GC_BENCH_PEAK(root_buf_peak, root_buf_length);
320 }
321 
gc_find_additional_buffer(zend_refcounted * ref)322 static zend_always_inline gc_root_buffer* gc_find_additional_buffer(zend_refcounted *ref)
323 {
324 	gc_additional_buffer *additional_buffer = GC_G(additional_buffer);
325 
326 	/* We have to check each additional_buffer to find which one holds the ref */
327 	while (additional_buffer) {
328 		uint32_t idx = GC_ADDRESS(GC_INFO(ref)) - GC_ROOT_BUFFER_MAX_ENTRIES;
329 		if (idx < additional_buffer->used) {
330 			gc_root_buffer *root = additional_buffer->buf + idx;
331 			if (root->ref == ref) {
332 				return root;
333 			}
334 		}
335 		additional_buffer = additional_buffer->next;
336 	}
337 
338 	ZEND_ASSERT(0);
339 	return NULL;
340 }
341 
gc_remove_from_buffer(zend_refcounted * ref)342 ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref)
343 {
344 	gc_root_buffer *root;
345 
346 	ZEND_ASSERT(GC_ADDRESS(GC_INFO(ref)));
347 
348 	GC_BENCH_INC(zval_remove_from_buffer);
349 
350 	if (EXPECTED(GC_ADDRESS(GC_INFO(ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
351 		root = GC_G(buf) + GC_ADDRESS(GC_INFO(ref));
352 		gc_remove_from_roots(root);
353 	} else {
354 		root = gc_find_additional_buffer(ref);
355 		gc_remove_from_additional_roots(root);
356 	}
357 	if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
358 		GC_TRACE_SET_COLOR(ref, GC_PURPLE);
359 	}
360 	GC_INFO(ref) = 0;
361 
362 	/* updete next root that is going to be freed */
363 	if (GC_G(next_to_free) == root) {
364 		GC_G(next_to_free) = root->next;
365 	}
366 }
367 
gc_scan_black(zend_refcounted * ref)368 static void gc_scan_black(zend_refcounted *ref)
369 {
370 	HashTable *ht;
371 	Bucket *p, *end;
372 	zval *zv;
373 
374 tail_call:
375 	ht = NULL;
376 	GC_REF_SET_BLACK(ref);
377 
378 	if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
379 		zend_object_get_gc_t get_gc;
380 		zend_object *obj = (zend_object*)ref;
381 
382 		if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
383 		             (get_gc = obj->handlers->get_gc) != NULL)) {
384 			int n;
385 			zval *zv, *end;
386 			zval tmp;
387 
388 			ZVAL_OBJ(&tmp, obj);
389 			ht = get_gc(&tmp, &zv, &n);
390 			end = zv + n;
391 			if (EXPECTED(!ht)) {
392 				if (!n) return;
393 				while (!Z_REFCOUNTED_P(--end)) {
394 					if (zv == end) return;
395 				}
396 			}
397 			while (zv != end) {
398 				if (Z_REFCOUNTED_P(zv)) {
399 					ref = Z_COUNTED_P(zv);
400 					GC_REFCOUNT(ref)++;
401 					if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
402 						gc_scan_black(ref);
403 					}
404 				}
405 				zv++;
406 			}
407 			if (EXPECTED(!ht)) {
408 				ref = Z_COUNTED_P(zv);
409 				GC_REFCOUNT(ref)++;
410 				if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
411 					goto tail_call;
412 				}
413 				return;
414 			}
415 		} else {
416 			return;
417 		}
418 	} else if (GC_TYPE(ref) == IS_ARRAY) {
419 		if ((zend_array*)ref != &EG(symbol_table)) {
420 			ht = (zend_array*)ref;
421 		} else {
422 			return;
423 		}
424 	} else if (GC_TYPE(ref) == IS_REFERENCE) {
425 		if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
426 			ref = Z_COUNTED(((zend_reference*)ref)->val);
427 			GC_REFCOUNT(ref)++;
428 			if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
429 				goto tail_call;
430 			}
431 		}
432 		return;
433 	} else {
434 		return;
435 	}
436 
437 	if (!ht->nNumUsed) return;
438 	p = ht->arData;
439 	end = p + ht->nNumUsed;
440 	while (1) {
441 		end--;
442 		zv = &end->val;
443 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
444 			zv = Z_INDIRECT_P(zv);
445 		}
446 		if (Z_REFCOUNTED_P(zv)) {
447 			break;
448 		}
449 		if (p == end) return;
450 	}
451 	while (p != end) {
452 		zv = &p->val;
453 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
454 			zv = Z_INDIRECT_P(zv);
455 		}
456 		if (Z_REFCOUNTED_P(zv)) {
457 			ref = Z_COUNTED_P(zv);
458 			GC_REFCOUNT(ref)++;
459 			if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
460 				gc_scan_black(ref);
461 			}
462 		}
463 		p++;
464 	}
465 	zv = &p->val;
466 	if (Z_TYPE_P(zv) == IS_INDIRECT) {
467 		zv = Z_INDIRECT_P(zv);
468 	}
469 	ref = Z_COUNTED_P(zv);
470 	GC_REFCOUNT(ref)++;
471 	if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
472 		goto tail_call;
473 	}
474 }
475 
gc_mark_grey(zend_refcounted * ref)476 static void gc_mark_grey(zend_refcounted *ref)
477 {
478     HashTable *ht;
479 	Bucket *p, *end;
480 	zval *zv;
481 
482 tail_call:
483 	if (GC_REF_GET_COLOR(ref) != GC_GREY) {
484 		ht = NULL;
485 		GC_BENCH_INC(zval_marked_grey);
486 		GC_REF_SET_COLOR(ref, GC_GREY);
487 
488 		if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
489 			zend_object_get_gc_t get_gc;
490 			zend_object *obj = (zend_object*)ref;
491 
492 			if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
493 		             (get_gc = obj->handlers->get_gc) != NULL)) {
494 				int n;
495 				zval *zv, *end;
496 				zval tmp;
497 
498 				ZVAL_OBJ(&tmp, obj);
499 				ht = get_gc(&tmp, &zv, &n);
500 				end = zv + n;
501 				if (EXPECTED(!ht)) {
502 					if (!n) return;
503 					while (!Z_REFCOUNTED_P(--end)) {
504 						if (zv == end) return;
505 					}
506 				}
507 				while (zv != end) {
508 					if (Z_REFCOUNTED_P(zv)) {
509 						ref = Z_COUNTED_P(zv);
510 						GC_REFCOUNT(ref)--;
511 						gc_mark_grey(ref);
512 					}
513 					zv++;
514 				}
515 				if (EXPECTED(!ht)) {
516 					ref = Z_COUNTED_P(zv);
517 					GC_REFCOUNT(ref)--;
518 					goto tail_call;
519 				}
520 			} else {
521 				return;
522 			}
523 		} else if (GC_TYPE(ref) == IS_ARRAY) {
524 			if (((zend_array*)ref) == &EG(symbol_table)) {
525 				GC_REF_SET_BLACK(ref);
526 				return;
527 			} else {
528 				ht = (zend_array*)ref;
529 			}
530 		} else if (GC_TYPE(ref) == IS_REFERENCE) {
531 			if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
532 				if (UNEXPECTED(!EG(objects_store).object_buckets) &&
533 					Z_TYPE(((zend_reference*)ref)->val) == IS_OBJECT) {
534 					Z_TYPE_INFO(((zend_reference*)ref)->val) = IS_NULL;
535 					return;
536 				}
537 				ref = Z_COUNTED(((zend_reference*)ref)->val);
538 				GC_REFCOUNT(ref)--;
539 				goto tail_call;
540 			}
541 			return;
542 		} else {
543 			return;
544 		}
545 
546 		if (!ht->nNumUsed) return;
547 		p = ht->arData;
548 		end = p + ht->nNumUsed;
549 		while (1) {
550 			end--;
551 			zv = &end->val;
552 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
553 				zv = Z_INDIRECT_P(zv);
554 			}
555 			if (Z_REFCOUNTED_P(zv)) {
556 				break;
557 			}
558 			if (p == end) return;
559 		}
560 		while (p != end) {
561 			zv = &p->val;
562 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
563 				zv = Z_INDIRECT_P(zv);
564 			}
565 			if (Z_REFCOUNTED_P(zv)) {
566 				if (Z_TYPE_P(zv) == IS_OBJECT &&
567 				    UNEXPECTED(!EG(objects_store).object_buckets)) {
568 					Z_TYPE_INFO_P(zv) = IS_NULL;
569 				} else {
570 					ref = Z_COUNTED_P(zv);
571 					GC_REFCOUNT(ref)--;
572 					gc_mark_grey(ref);
573 				}
574 			}
575 			p++;
576 		}
577 		zv = &p->val;
578 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
579 			zv = Z_INDIRECT_P(zv);
580 		}
581 		if (Z_TYPE_P(zv) == IS_OBJECT &&
582 		    UNEXPECTED(!EG(objects_store).object_buckets)) {
583 			Z_TYPE_INFO_P(zv) = IS_NULL;
584 		} else {
585 			ref = Z_COUNTED_P(zv);
586 			GC_REFCOUNT(ref)--;
587 			goto tail_call;
588 		}
589 	}
590 }
591 
gc_mark_roots(void)592 static void gc_mark_roots(void)
593 {
594 	gc_root_buffer *current = GC_G(roots).next;
595 
596 	while (current != &GC_G(roots)) {
597 		if (GC_REF_GET_COLOR(current->ref) == GC_PURPLE) {
598 			gc_mark_grey(current->ref);
599 		}
600 		current = current->next;
601 	}
602 }
603 
gc_scan(zend_refcounted * ref)604 static void gc_scan(zend_refcounted *ref)
605 {
606     HashTable *ht;
607 	Bucket *p, *end;
608 	zval *zv;
609 
610 tail_call:
611 	if (GC_REF_GET_COLOR(ref) == GC_GREY) {
612 		if (GC_REFCOUNT(ref) > 0) {
613 			gc_scan_black(ref);
614 		} else {
615 			GC_REF_SET_COLOR(ref, GC_WHITE);
616 			if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
617 				zend_object_get_gc_t get_gc;
618 				zend_object *obj = (zend_object*)ref;
619 
620 				if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
621 				             (get_gc = obj->handlers->get_gc) != NULL)) {
622 					int n;
623 					zval *zv, *end;
624 					zval tmp;
625 
626 					ZVAL_OBJ(&tmp, obj);
627 					ht = get_gc(&tmp, &zv, &n);
628 					end = zv + n;
629 					if (EXPECTED(!ht)) {
630 						if (!n) return;
631 						while (!Z_REFCOUNTED_P(--end)) {
632 							if (zv == end) return;
633 						}
634 					}
635 					while (zv != end) {
636 						if (Z_REFCOUNTED_P(zv)) {
637 							ref = Z_COUNTED_P(zv);
638 							gc_scan(ref);
639 						}
640 						zv++;
641 					}
642 					if (EXPECTED(!ht)) {
643 						ref = Z_COUNTED_P(zv);
644 						goto tail_call;
645 					}
646 				} else {
647 					return;
648 				}
649 			} else if (GC_TYPE(ref) == IS_ARRAY) {
650 				if ((zend_array*)ref == &EG(symbol_table)) {
651 					GC_REF_SET_BLACK(ref);
652 					return;
653 				} else {
654 					ht = (zend_array*)ref;
655 				}
656 			} else if (GC_TYPE(ref) == IS_REFERENCE) {
657 				if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
658 					ref = Z_COUNTED(((zend_reference*)ref)->val);
659 					goto tail_call;
660 				}
661 				return;
662 			} else {
663 				return;
664 			}
665 
666 			if (!ht->nNumUsed) return;
667 			p = ht->arData;
668 			end = p + ht->nNumUsed;
669 			while (1) {
670 				end--;
671 				zv = &end->val;
672 				if (Z_TYPE_P(zv) == IS_INDIRECT) {
673 					zv = Z_INDIRECT_P(zv);
674 				}
675 				if (Z_REFCOUNTED_P(zv)) {
676 					break;
677 				}
678 				if (p == end) return;
679 			}
680 			while (p != end) {
681 				zv = &p->val;
682 				if (Z_TYPE_P(zv) == IS_INDIRECT) {
683 					zv = Z_INDIRECT_P(zv);
684 				}
685 				if (Z_REFCOUNTED_P(zv)) {
686 					ref = Z_COUNTED_P(zv);
687 					gc_scan(ref);
688 				}
689 				p++;
690 			}
691 			zv = &p->val;
692 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
693 				zv = Z_INDIRECT_P(zv);
694 			}
695 			ref = Z_COUNTED_P(zv);
696 			goto tail_call;
697 		}
698 	}
699 }
700 
gc_scan_roots(void)701 static void gc_scan_roots(void)
702 {
703 	gc_root_buffer *current = GC_G(roots).next;
704 
705 	while (current != &GC_G(roots)) {
706 		gc_scan(current->ref);
707 		current = current->next;
708 	}
709 }
710 
gc_add_garbage(zend_refcounted * ref)711 static void gc_add_garbage(zend_refcounted *ref)
712 {
713 	gc_root_buffer *buf = GC_G(unused);
714 
715 	if (buf) {
716 		GC_G(unused) = buf->prev;
717 #if 1
718 		/* optimization: color is already GC_BLACK (0) */
719 		GC_INFO(ref) = buf - GC_G(buf);
720 #else
721 		GC_REF_SET_ADDRESS(ref, buf - GC_G(buf));
722 #endif
723 	} else if (GC_G(first_unused) != GC_G(last_unused)) {
724 		buf = GC_G(first_unused);
725 		GC_G(first_unused)++;
726 #if 1
727 		/* optimization: color is already GC_BLACK (0) */
728 		GC_INFO(ref) = buf - GC_G(buf);
729 #else
730 		GC_REF_SET_ADDRESS(ref, buf - GC_G(buf));
731 #endif
732 	} else {
733 		/* If we don't have free slots in the buffer, allocate a new one and
734 		 * set it's address above GC_ROOT_BUFFER_MAX_ENTRIES that have special
735 		 * meaning.
736 		 */
737 		if (!GC_G(additional_buffer) || GC_G(additional_buffer)->used == GC_NUM_ADDITIONAL_ENTRIES) {
738 			gc_additional_buffer *new_buffer = emalloc(sizeof(gc_additional_buffer));
739 			new_buffer->used = 0;
740 			new_buffer->next = GC_G(additional_buffer);
741 			GC_G(additional_buffer) = new_buffer;
742 		}
743 		buf = GC_G(additional_buffer)->buf + GC_G(additional_buffer)->used;
744 #if 1
745 		/* optimization: color is already GC_BLACK (0) */
746 		GC_INFO(ref) = GC_ROOT_BUFFER_MAX_ENTRIES + GC_G(additional_buffer)->used;
747 #else
748 		GC_REF_SET_ADDRESS(ref, GC_ROOT_BUFFER_MAX_ENTRIES) + GC_G(additional_buffer)->used;
749 #endif
750 		GC_G(additional_buffer)->used++;
751 	}
752 	if (buf) {
753 		buf->ref = ref;
754 		buf->next = GC_G(roots).next;
755 		buf->prev = &GC_G(roots);
756 		GC_G(roots).next->prev = buf;
757 		GC_G(roots).next = buf;
758 	}
759 }
760 
gc_collect_white(zend_refcounted * ref,uint32_t * flags)761 static int gc_collect_white(zend_refcounted *ref, uint32_t *flags)
762 {
763 	int count = 0;
764 	HashTable *ht;
765 	Bucket *p, *end;
766 	zval *zv;
767 
768 tail_call:
769 	if (GC_REF_GET_COLOR(ref) == GC_WHITE) {
770 		ht = NULL;
771 		GC_REF_SET_BLACK(ref);
772 
773 		/* don't count references for compatibility ??? */
774 		if (GC_TYPE(ref) != IS_REFERENCE) {
775 			count++;
776 		}
777 
778 		if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
779 			zend_object_get_gc_t get_gc;
780 			zend_object *obj = (zend_object*)ref;
781 
782 			if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
783 			             (get_gc = obj->handlers->get_gc) != NULL)) {
784 				int n;
785 				zval *zv, *end;
786 				zval tmp;
787 
788 #if 1
789 				/* optimization: color is GC_BLACK (0) */
790 				if (!GC_INFO(ref)) {
791 #else
792 				if (!GC_ADDRESS(GC_INFO(ref))) {
793 #endif
794 					gc_add_garbage(ref);
795 				}
796 				if (obj->handlers->dtor_obj &&
797 				    ((obj->handlers->dtor_obj != zend_objects_destroy_object) ||
798 				     (obj->ce->destructor != NULL))) {
799 					*flags |= GC_HAS_DESTRUCTORS;
800 				}
801 				ZVAL_OBJ(&tmp, obj);
802 				ht = get_gc(&tmp, &zv, &n);
803 				end = zv + n;
804 				if (EXPECTED(!ht)) {
805 					if (!n) return count;
806 					while (!Z_REFCOUNTED_P(--end)) {
807 						/* count non-refcounted for compatibility ??? */
808 						if (Z_TYPE_P(zv) != IS_UNDEF) {
809 							count++;
810 						}
811 						if (zv == end) return count;
812 					}
813 				}
814 				while (zv != end) {
815 					if (Z_REFCOUNTED_P(zv)) {
816 						ref = Z_COUNTED_P(zv);
817 						GC_REFCOUNT(ref)++;
818 						count += gc_collect_white(ref, flags);
819 					/* count non-refcounted for compatibility ??? */
820 					} else if (Z_TYPE_P(zv) != IS_UNDEF) {
821 						count++;
822 					}
823 					zv++;
824 				}
825 				if (EXPECTED(!ht)) {
826 					ref = Z_COUNTED_P(zv);
827 					GC_REFCOUNT(ref)++;
828 					goto tail_call;
829 				}
830 			} else {
831 				return count;
832 			}
833 		} else if (GC_TYPE(ref) == IS_ARRAY) {
834 #if 1
835 				/* optimization: color is GC_BLACK (0) */
836 				if (!GC_INFO(ref)) {
837 #else
838 				if (!GC_ADDRESS(GC_INFO(ref))) {
839 #endif
840 				gc_add_garbage(ref);
841 			}
842 			ht = (zend_array*)ref;
843 		} else if (GC_TYPE(ref) == IS_REFERENCE) {
844 			if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
845 				ref = Z_COUNTED(((zend_reference*)ref)->val);
846 				GC_REFCOUNT(ref)++;
847 				goto tail_call;
848 			}
849 			return count;
850 		} else {
851 			return count;
852 		}
853 
854 		if (!ht->nNumUsed) return count;
855 		p = ht->arData;
856 		end = p + ht->nNumUsed;
857 		while (1) {
858 			end--;
859 			zv = &end->val;
860 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
861 				zv = Z_INDIRECT_P(zv);
862 			}
863 			if (Z_REFCOUNTED_P(zv)) {
864 				break;
865 			}
866 			/* count non-refcounted for compatibility ??? */
867 			if (Z_TYPE_P(zv) != IS_UNDEF) {
868 				count++;
869 			}
870 			if (p == end) return count;
871 		}
872 		while (p != end) {
873 			zv = &p->val;
874 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
875 				zv = Z_INDIRECT_P(zv);
876 			}
877 			if (Z_REFCOUNTED_P(zv)) {
878 				ref = Z_COUNTED_P(zv);
879 				GC_REFCOUNT(ref)++;
880 				count += gc_collect_white(ref, flags);
881 				/* count non-refcounted for compatibility ??? */
882 			} else if (Z_TYPE_P(zv) != IS_UNDEF) {
883 				count++;
884 			}
885 			p++;
886 		}
887 		zv = &p->val;
888 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
889 			zv = Z_INDIRECT_P(zv);
890 		}
891 		ref = Z_COUNTED_P(zv);
892 		GC_REFCOUNT(ref)++;
893 		goto tail_call;
894 	}
895 	return count;
896 }
897 
898 static int gc_collect_roots(uint32_t *flags)
899 {
900 	int count = 0;
901 	gc_root_buffer *current = GC_G(roots).next;
902 
903 	/* remove non-garbage from the list */
904 	while (current != &GC_G(roots)) {
905 		gc_root_buffer *next = current->next;
906 		if (GC_REF_GET_COLOR(current->ref) == GC_BLACK) {
907 			if (EXPECTED(GC_ADDRESS(GC_INFO(current->ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
908 				gc_remove_from_roots(current);
909 			} else {
910 				gc_remove_from_additional_roots(current);
911 			}
912 			GC_INFO(current->ref) = 0; /* reset GC_ADDRESS() and keep GC_BLACK */
913 		}
914 		current = next;
915 	}
916 
917 	current = GC_G(roots).next;
918 	while (current != &GC_G(roots)) {
919 		if (GC_REF_GET_COLOR(current->ref) == GC_WHITE) {
920 			count += gc_collect_white(current->ref, flags);
921 		}
922 		current = current->next;
923 	}
924 
925 	/* relink remaining roots into list to free */
926 	if (GC_G(roots).next != &GC_G(roots)) {
927 		if (GC_G(to_free).next == &GC_G(to_free)) {
928 			/* move roots into list to free */
929 			GC_G(to_free).next = GC_G(roots).next;
930 			GC_G(to_free).prev = GC_G(roots).prev;
931 			GC_G(to_free).next->prev = &GC_G(to_free);
932 			GC_G(to_free).prev->next = &GC_G(to_free);
933 		} else {
934 			/* add roots into list to free */
935 			GC_G(to_free).prev->next = GC_G(roots).next;
936 			GC_G(roots).next->prev = GC_G(to_free).prev;
937 			GC_G(roots).prev->next = &GC_G(to_free);
938 			GC_G(to_free).prev = GC_G(roots).prev;
939 		}
940 
941 		GC_G(roots).next = &GC_G(roots);
942 		GC_G(roots).prev = &GC_G(roots);
943 	}
944 	return count;
945 }
946 
947 static void gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root)
948 {
949 	HashTable *ht = NULL;
950 	Bucket *p, *end;
951 	zval *zv;
952 
953 tail_call:
954 	if (root ||
955 	    (GC_ADDRESS(GC_INFO(ref)) != 0 &&
956 	     GC_REF_GET_COLOR(ref) == GC_BLACK)) {
957 		GC_TRACE_REF(ref, "removing from buffer");
958 		if (root) {
959 			if (EXPECTED(GC_ADDRESS(GC_INFO(root->ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
960 				gc_remove_from_roots(root);
961 			} else {
962 				gc_remove_from_additional_roots(root);
963 			}
964 			GC_INFO(ref) = 0;
965 			root = NULL;
966 		} else {
967 			GC_REMOVE_FROM_BUFFER(ref);
968 		}
969 
970 		if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
971 			zend_object_get_gc_t get_gc;
972 			zend_object *obj = (zend_object*)ref;
973 
974 			if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
975 		             (get_gc = obj->handlers->get_gc) != NULL)) {
976 				int n;
977 				zval *zv, *end;
978 				zval tmp;
979 
980 				ZVAL_OBJ(&tmp, obj);
981 				ht = get_gc(&tmp, &zv, &n);
982 				end = zv + n;
983 				if (EXPECTED(!ht)) {
984 					if (!n) return;
985 					while (!Z_REFCOUNTED_P(--end)) {
986 						if (zv == end) return;
987 					}
988 				}
989 				while (zv != end) {
990 					if (Z_REFCOUNTED_P(zv)) {
991 						ref = Z_COUNTED_P(zv);
992 						gc_remove_nested_data_from_buffer(ref, NULL);
993 					}
994 					zv++;
995 				}
996 				if (EXPECTED(!ht)) {
997 					ref = Z_COUNTED_P(zv);
998 					goto tail_call;
999 				}
1000 			} else {
1001 				return;
1002 			}
1003 		} else if (GC_TYPE(ref) == IS_ARRAY) {
1004 			ht = (zend_array*)ref;
1005 		} else if (GC_TYPE(ref) == IS_REFERENCE) {
1006 			if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
1007 				ref = Z_COUNTED(((zend_reference*)ref)->val);
1008 				goto tail_call;
1009 			}
1010 			return;
1011 		} else {
1012 			return;
1013 		}
1014 
1015 		if (!ht->nNumUsed) return;
1016 		p = ht->arData;
1017 		end = p + ht->nNumUsed;
1018 		while (1) {
1019 			end--;
1020 			zv = &end->val;
1021 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
1022 				zv = Z_INDIRECT_P(zv);
1023 			}
1024 			if (Z_REFCOUNTED_P(zv)) {
1025 				break;
1026 			}
1027 			if (p == end) return;
1028 		}
1029 		while (p != end) {
1030 			zv = &p->val;
1031 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
1032 				zv = Z_INDIRECT_P(zv);
1033 			}
1034 			if (Z_REFCOUNTED_P(zv)) {
1035 				ref = Z_COUNTED_P(zv);
1036 				gc_remove_nested_data_from_buffer(ref, NULL);
1037 			}
1038 			p++;
1039 		}
1040 		zv = &p->val;
1041 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
1042 			zv = Z_INDIRECT_P(zv);
1043 		}
1044 		ref = Z_COUNTED_P(zv);
1045 		goto tail_call;
1046 	}
1047 }
1048 
1049 ZEND_API int zend_gc_collect_cycles(void)
1050 {
1051 	int count = 0;
1052 
1053 	if (GC_G(roots).next != &GC_G(roots)) {
1054 		gc_root_buffer *current, *next, *orig_next_to_free;
1055 		zend_refcounted *p;
1056 		gc_root_buffer to_free;
1057 		uint32_t gc_flags = 0;
1058 		gc_additional_buffer *additional_buffer_snapshot;
1059 #if ZEND_GC_DEBUG
1060 		zend_bool orig_gc_full;
1061 #endif
1062 
1063 		if (GC_G(gc_active)) {
1064 			return 0;
1065 		}
1066 
1067 		GC_TRACE("Collecting cycles");
1068 		GC_G(gc_runs)++;
1069 		GC_G(gc_active) = 1;
1070 
1071 		GC_TRACE("Marking roots");
1072 		gc_mark_roots();
1073 		GC_TRACE("Scanning roots");
1074 		gc_scan_roots();
1075 
1076 #if ZEND_GC_DEBUG
1077 		orig_gc_full = GC_G(gc_full);
1078 		GC_G(gc_full) = 0;
1079 #endif
1080 
1081 		GC_TRACE("Collecting roots");
1082 		additional_buffer_snapshot = GC_G(additional_buffer);
1083 		count = gc_collect_roots(&gc_flags);
1084 #if ZEND_GC_DEBUG
1085 		GC_G(gc_full) = orig_gc_full;
1086 #endif
1087 		GC_G(gc_active) = 0;
1088 
1089 		if (GC_G(to_free).next == &GC_G(to_free)) {
1090 			/* nothing to free */
1091 			GC_TRACE("Nothing to free");
1092 			return 0;
1093 		}
1094 
1095 		/* Copy global to_free list into local list */
1096 		to_free.next = GC_G(to_free).next;
1097 		to_free.prev = GC_G(to_free).prev;
1098 		to_free.next->prev = &to_free;
1099 		to_free.prev->next = &to_free;
1100 
1101 		/* Free global list */
1102 		GC_G(to_free).next = &GC_G(to_free);
1103 		GC_G(to_free).prev = &GC_G(to_free);
1104 
1105 		orig_next_to_free = GC_G(next_to_free);
1106 
1107 #if ZEND_GC_DEBUG
1108 		orig_gc_full = GC_G(gc_full);
1109 		GC_G(gc_full) = 0;
1110 #endif
1111 
1112 		if (gc_flags & GC_HAS_DESTRUCTORS) {
1113 			GC_TRACE("Calling destructors");
1114 			if (EG(objects_store).object_buckets) {
1115 				/* Remember reference counters before calling destructors */
1116 				current = to_free.next;
1117 				while (current != &to_free) {
1118 					current->refcount = GC_REFCOUNT(current->ref);
1119 					current = current->next;
1120 				}
1121 
1122 				/* Call destructors */
1123 				current = to_free.next;
1124 				while (current != &to_free) {
1125 					p = current->ref;
1126 					GC_G(next_to_free) = current->next;
1127 					if (GC_TYPE(p) == IS_OBJECT) {
1128 						zend_object *obj = (zend_object*)p;
1129 
1130 						if (IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
1131 							!(GC_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) {
1132 							GC_TRACE_REF(obj, "calling destructor");
1133 							GC_FLAGS(obj) |= IS_OBJ_DESTRUCTOR_CALLED;
1134 							if (obj->handlers->dtor_obj) {
1135 								GC_REFCOUNT(obj)++;
1136 								obj->handlers->dtor_obj(obj);
1137 								GC_REFCOUNT(obj)--;
1138 							}
1139 						}
1140 					}
1141 					current = GC_G(next_to_free);
1142 				}
1143 
1144 				/* Remove values captured in destructors */
1145 				current = to_free.next;
1146 				while (current != &to_free) {
1147 					GC_G(next_to_free) = current->next;
1148 					if (GC_REFCOUNT(current->ref) > current->refcount) {
1149 						gc_remove_nested_data_from_buffer(current->ref, current);
1150 					}
1151 					current = GC_G(next_to_free);
1152 				}
1153 			}
1154 		}
1155 
1156 		/* Destroy zvals */
1157 		GC_TRACE("Destroying zvals");
1158 		GC_G(gc_active) = 1;
1159 		current = to_free.next;
1160 		while (current != &to_free) {
1161 			p = current->ref;
1162 			GC_G(next_to_free) = current->next;
1163 			GC_TRACE_REF(p, "destroying");
1164 			if (GC_TYPE(p) == IS_OBJECT) {
1165 				zend_object *obj = (zend_object*)p;
1166 
1167 				if (EG(objects_store).object_buckets &&
1168 				    IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle])) {
1169 					EG(objects_store).object_buckets[obj->handle] = SET_OBJ_INVALID(obj);
1170 					GC_TYPE(obj) = IS_NULL;
1171 					if (!(GC_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {
1172 						GC_FLAGS(obj) |= IS_OBJ_FREE_CALLED;
1173 						if (obj->handlers->free_obj) {
1174 							GC_REFCOUNT(obj)++;
1175 							obj->handlers->free_obj(obj);
1176 							GC_REFCOUNT(obj)--;
1177 						}
1178 					}
1179 					SET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[obj->handle], EG(objects_store).free_list_head);
1180 					EG(objects_store).free_list_head = obj->handle;
1181 					p = current->ref = (zend_refcounted*)(((char*)obj) - obj->handlers->offset);
1182 				}
1183 			} else if (GC_TYPE(p) == IS_ARRAY) {
1184 				zend_array *arr = (zend_array*)p;
1185 
1186 				GC_TYPE(arr) = IS_NULL;
1187 				zend_hash_destroy(arr);
1188 			}
1189 			current = GC_G(next_to_free);
1190 		}
1191 
1192 		/* Free objects */
1193 		current = to_free.next;
1194 		while (current != &to_free) {
1195 			next = current->next;
1196 			p = current->ref;
1197 			if (EXPECTED(current >= GC_G(buf) && current < GC_G(buf) + GC_ROOT_BUFFER_MAX_ENTRIES)) {
1198 				current->prev = GC_G(unused);
1199 				GC_G(unused) = current;
1200 			}
1201 			efree(p);
1202 			current = next;
1203 		}
1204 
1205 		while (GC_G(additional_buffer) != additional_buffer_snapshot) {
1206 			gc_additional_buffer *next = GC_G(additional_buffer)->next;
1207 			efree(GC_G(additional_buffer));
1208 			GC_G(additional_buffer) = next;
1209 		}
1210 
1211 		GC_TRACE("Collection finished");
1212 		GC_G(collected) += count;
1213 		GC_G(next_to_free) = orig_next_to_free;
1214 #if ZEND_GC_DEBUG
1215 		GC_G(gc_full) = orig_gc_full;
1216 #endif
1217 		GC_G(gc_active) = 0;
1218 	}
1219 
1220 	return count;
1221 }
1222 
1223 /*
1224  * Local variables:
1225  * tab-width: 4
1226  * c-basic-offset: 4
1227  * indent-tabs-mode: t
1228  * End:
1229  *
1230  * vim:noexpandtab:
1231  */
1232