File: REFCOUNTING.md

package info (click to toggle)
mysql-8.0 8.0.43-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,273,904 kB
  • sloc: cpp: 4,684,605; ansic: 412,450; pascal: 108,398; java: 83,641; perl: 30,221; cs: 27,067; sql: 26,594; sh: 24,184; python: 21,816; yacc: 17,169; php: 11,522; xml: 7,388; javascript: 7,076; makefile: 2,196; lex: 1,075; awk: 670; asm: 520; objc: 183; ruby: 97; lisp: 86
file content (112 lines) | stat: -rw-r--r-- 3,762 bytes parent folder | download | duplicates (14)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

# Refcounting Tips

One of the trickiest parts of the C extension for PHP is getting the refcounting
right.  These are some notes about the basics of what you should know,
especially if you're not super familiar with PHP's C API.

These notes cover the same general material as [the Memory Management chapter of
the PHP internal's
book](https://www.phpinternalsbook.com/php7/zvals/memory_management.html), but
calls out some points that were not immediately clear to me.

##  Zvals

In the PHP C API, the `zval` type is roughly analogous to a variable in PHP, eg:

```php
    // Think of $a as a "zval".
    $a = [];
```

The equivalent PHP C code would be:

```c
    zval a;
    ZVAL_NEW_ARR(&a);  // Allocates and assigns a new array.
```

PHP is reference counted, so each variable -- and thus each zval -- will have a
reference on whatever it points to (unless its holding a data type that isn't
refcounted at all, like numbers). Since the zval owns a reference, it must be
explicitly destroyed in order to release this reference.

```c
    zval a;
    ZVAL_NEW_ARR(&a);

    // The destructor for a zval, this must be called or the ref will be leaked.
    zval_ptr_dtor(&a);
```

Whenever you see a `zval`, you can assume it owns a ref (or is storing a
non-refcounted type). If you see a `zval*`, which is also quite common, then
this is *pointing to* something that owns a ref, but it does not own a ref
itself.

The [`ZVAL_*` family of
macros](https://github.com/php/php-src/blob/4030a00e8b6453aff929362bf9b25c193f72c94a/Zend/zend_types.h#L883-L1109)
initializes a `zval` from a specific value type.  A few examples:

* `ZVAL_NULL(&zv)`: initializes the value to `null`
* `ZVAL_LONG(&zv, 5)`: initializes a `zend_long` (integer) value
* `ZVAL_ARR(&zv, arr)`: initializes a `zend_array*` value (refcounted)
* `ZVAL_OBJ(&zv, obj)`: initializes a `zend_object*` value (refcounted)

Note that all of our custom objects (messages, repeated fields, descriptors,
etc) are `zend_object*`.

The variants that initialize from a refcounted type do *not* increase the
refcount. This makes them suitable for initializing from a newly-created object:

```c
    zval zv;
    ZVAL_OBJ(&zv, CreateObject());
```

Once in a while, we want to initialize a `zval` while also increasing the
reference count. For this we can use `ZVAL_OBJ_COPY()`:

```c
zend_object *some_global;

void GetGlobal(zval *zv) {
    // We want to create a new ref to an existing object.
    ZVAL_OBJ_COPY(zv, some_global);
}
```

## Transferring references

A `zval`'s ref must be released at some point. While `zval_ptr_dtor()` is the
simplest way of releasing a ref, it is not the most common (at least in our code
base). More often, we are returning the `zval` back to PHP from C.

```c
    zval zv;
    InitializeOurZval(&zv);
    // Returns the value of zv to the caller and donates our ref.
    RETURN_COPY_VALUE(&zv);
```

The `RETURN_COPY_VALUE()` macro (standard in PHP 8.x, and polyfilled in earlier
versions) is the most common way we return a value back to PHP, because it
donates our `zval`'s refcount to the caller, and thus saves us from needing to
destroy our `zval` explicitly. This is ideal when we have a full `zval` to
return.

Once in a while we have a `zval*` to return instead. For example when we parse
parameters to our function and ask for a `zval`, PHP will give us pointers to
the existing `zval` structures instead of creating new ones.

```c
    zval *val;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &val) == FAILURE) {
      return;
    }
    // Returns a copy of this zval, adding a ref in the process.
    RETURN_COPY(val);
```

When we use `RETURN_COPY`, the refcount is increased; this is perfect for
returning a `zval*` when we do not own a ref on it.