Coverage Report

Created: 2026-06-02 06:40

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