Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ jobs:
uses: actions/checkout@v6
- name: Install dependencies
run: pip install -r docs/requirements.txt
- name: Check formatting
run: make -C docs check-formatting
- name: Publish
if: github.event_name == 'push'
uses: sphinx-notes/pages@v3
Expand Down
20 changes: 3 additions & 17 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,16 @@ SPHINXBUILD ?= sphinx-build

SOURCEDIR = source
BUILDDIR = build
RSTFMT = rstfmt
RSTFMTFLAGS = -w 100

rwildcard = $(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
FILES = $(call rwildcard,$(SOURCEDIR),*.rst)

all : html

.PHONY : check-formatting clean html preflight
.PHONY : clean html
.SUFFIXES : # Disable legacy behavior

check-formatting :
$(RSTFMT) $(RSTFMTFLAGS) --check $(SOURCEDIR)

clean :
rm -rf -- $(wildcard $(SOURCEDIR)/.~ $(BUILDDIR))
rm -rf -- $(BUILDDIR)

html : preflight
html :
$(SPHINXBUILD) -M $@ $(SOURCEDIR) $(BUILDDIR)
@printf 'Browse the \e]8;;%s\e\\%s\e]8;;\e\\.\n' \
"file://$(abspath $(BUILDDIR))/$@/index.$@" "php-src html docs locally"

preflight : $(SOURCEDIR)/.~

$(SOURCEDIR)/.~ : $(FILES)
$(RSTFMT) $(RSTFMTFLAGS) $?
touch $@
10 changes: 1 addition & 9 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,4 @@ your browser.

## Formatting

The files in this documentation are formatted using the
[``rstfmt``](https://github.com/dzhu/rstfmt) tool.

```bash
rstfmt -w 100 source
```

This tool is not perfect. It breaks on custom directives, so we might switch to
either a fork or something else in the future.
Formatting is temporarily not enforced during the Markdown migration.
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Sphinx
myst-parser>=5.1
sphinx-design
sphinxawesome-theme
rstfmt
9 changes: 9 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@
project = 'php-src docs'
author = 'The PHP Group'
extensions = [
'myst_parser',
'sphinx_design',
'sphinx.ext.autosectionlabel',
]
myst_enable_extensions = [
'alert',
'gfm_autolink',
'strikethrough',
'tasklist',
]
myst_heading_anchors = 6
source_suffix = {'.rst': 'markdown'}
templates_path = ['_templates']
html_theme = 'sphinxawesome_theme'
html_static_path = ['_static']
Expand Down
15 changes: 7 additions & 8 deletions docs/source/core/data-structures/index.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
#################
Data structures
#################
# Data structures

.. toctree::
```{toctree}
:hidden:
zval
reference-counting
zend_string
zend_constant
zval
reference-counting
zend_string
zend_constant
```

This section provides an overview of the core data structures used in php-src.
133 changes: 65 additions & 68 deletions docs/source/core/data-structures/reference-counting.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
####################
Reference counting
####################
# Reference counting

In languages like C, when you need memory for storing data for an indefinite period of time or in a
large amount, you call ``malloc`` and ``free`` to acquire and release blocks of memory of some size.
large amount, you call `malloc` and `free` to acquire and release blocks of memory of some size.
This sounds simple on the surface but turns out to be quite tricky, mainly because the data may not
be freed for as long as it is used anywhere in the program. Sometimes this makes it unclear who is
responsible for freeing the memory, and when to do so. Failure to handle this correctly may result
Expand All @@ -17,12 +15,13 @@ used by another party. When the party no longer needs the value, it is responsib
the reference count. Once the reference count reaches zero, we know the value is no longer needed
anywhere, and that it may be freed.

.. code:: php
```php
$a = new stdClass; // RC 1
$b = $a; // RC 2
unset($a); // RC 1
unset($b); // RC 0, free
```

Reference counting is needed for types that store auxiliary data, which are the following:

Expand All @@ -33,13 +32,13 @@ Reference counting is needed for types that store auxiliary data, which are the
- Resources

These are either reference types (objects, references and resources) or they are large types that
don't fit in a single ``zend_value`` directly (strings, arrays). Simpler types either don't store a
value at all (``null``, ``false``, ``true``) or their value is small enough to fit directly in
``zend_value`` (``int``, ``float``).
don't fit in a single `zend_value` directly (strings, arrays). Simpler types either don't store a
value at all (`null`, `false`, `true`) or their value is small enough to fit directly in
`zend_value` (`int`, `float`).

All of the reference counted types share a common initial struct sequence.

.. code:: c
```c
typedef struct _zend_refcounted_h {
uint32_t refcount; /* reference counter 32-bit */
Expand All @@ -57,79 +56,80 @@ All of the reference counted types share a common initial struct sequence.
zend_refcounted_h gc;
// ...
};
```

The ``zend_refcounted_h`` struct is simple. It contains the reference count, and a ``type_info``
field that repeats some of the type information that is also stored in the ``zval``, for situations
where we're not dealing with a ``zval`` directly. It also stores some additional fields, described
under `GC flags`_.
The `zend_refcounted_h` struct is simple. It contains the reference count, and a `type_info`
field that repeats some of the type information that is also stored in the `zval`, for situations
where we're not dealing with a `zval` directly. It also stores some additional fields, described
under [GC flags](#gc-flags).

********
Macros
********
## Macros

As with ``zval``, ``zend_refcounted_h`` members should not be accessed directly. Instead, you should
As with `zval`, `zend_refcounted_h` members should not be accessed directly. Instead, you should
use the provided macros. There are macros that work with reference counted types directly, prefixed
with ``GC_``, or macros that work on ``zval`` values, usually prefixed with ``Z_``. Unfortunately,
with `GC_`, or macros that work on `zval` values, usually prefixed with `Z_`. Unfortunately,
naming is not always consistent.

.. list-table:: ``zval`` macros
~~~{list-table} `zval` macros
:header-rows: 1

- - Macro
- Non-RC [#non-rc]_
- Non-RC [^non-rc]
- Description

- - ``Z_REFCOUNT[_P]``
- - `Z_REFCOUNT[_P]`
- No
- Returns the reference count.

- - ``Z_ADDREF[_P]``
- - `Z_ADDREF[_P]`
- No
- Increases the reference count.

- - ``Z_TRY_ADDREF[_P]``
- - `Z_TRY_ADDREF[_P]`
- Yes
- Increases the reference count. May be called on any ``zval``.
- Increases the reference count. May be called on any `zval`.

- - ``zval_ptr_dtor``
- - `zval_ptr_dtor`
- Yes
- Decreases the reference count and frees the value if the reference count reaches zero.

.. [#non-rc]
~~~

Whether the macro works with non-reference counted types. If it does, the operation is usually a
no-op. If it does not, using the macro on these values is undefined behavior.
[^non-rc]:

.. list-table:: ``zend_refcounted_h`` macros
Whether the macro works with non-reference counted types. If it does, the operation is usually a
no-op. If it does not, using the macro on these values is undefined behavior.

~~~{list-table} `zend_refcounted_h` macros
:header-rows: 1

- - Macro
- Immutable [#immutable]_
- Immutable [^immutable]
- Description

- - ``GC_REFCOUNT[_P]``
- - `GC_REFCOUNT[_P]`
- Yes
- Returns the reference count.

- - ``GC_ADDREF[_P]``
- - `GC_ADDREF[_P]`
- No
- Increases the reference count.

- - ``GC_TRY_ADDREF[_P]``
- - `GC_TRY_ADDREF[_P]`
- Yes
- Increases the reference count.

- - ``GC_DTOR[_P]``
- - `GC_DTOR[_P]`
- Yes
- Decreases the reference count and frees the value if the reference count reaches zero.

.. [#immutable]
~~~

[^immutable]:

Whether the macro works with immutable types, described under `Immutable reference counted types`_.
Whether the macro works with immutable types, described under [Immutable reference counted types](#immutable-reference-counted-types).

************
Separation
************
## Separation

PHP has value and reference types. Reference types are types that are shared through a reference, a
"pointer" to the value, rather than the value itself. Modifying such a value in one place changes it
Expand All @@ -143,86 +143,83 @@ the value is not observable from other places. Modifying a value with RC 1 is un
we are the values sole owner. However, if the value has a reference count of >1, we need to create a
fresh copy before modifying it. This process is called separation or CoW (copy on write).

.. code:: php
```php
$a = [1, 2, 3]; // RC 1
$b = $a; // RC 2
$b[] = 4; // Separation, $a RC 1, $b RC 1
var_dump($a); // [1, 2, 3]
var_dump($b); // [1, 2, 3, 4]
```

***********************************
Immutable reference counted types
***********************************
## Immutable reference counted types

Sometimes, even a reference counted type is not reference counted. When PHP runs in a multi-process
or multi-threaded environment with opcache enabled, it shares some common values between processes
or threads to reduce memory consumption. As you may know, sharing memory between processes or
threads can be tricky and requires special care when modifying values. In particular, modification
usually requires exclusive access to the memory so that the other processes or threads wait until
the value is done being updated. In this case, this synchronization is avoided by making the value
immutable and never modifying the reference count. Such values will receive the ``GC_IMMUTABLE``
flag in their ``gc->u.type_info`` field.
immutable and never modifying the reference count. Such values will receive the `GC_IMMUTABLE`
flag in their `gc->u.type_info` field.

Some macros like ``GC_TRY_ADDREF`` will guard against immutable values. You should not use immutable
values on some macros, like ``GC_ADDREF``. This will result in undefined behavior, because the macro
Some macros like `GC_TRY_ADDREF` will guard against immutable values. You should not use immutable
values on some macros, like `GC_ADDREF`. This will result in undefined behavior, because the macro
will not check whether the value is immutable before performing the reference count modifications.
You may execute PHP with the ``-d opcache.protect_memory=1`` flag to mark the shared memory as
You may execute PHP with the `-d opcache.protect_memory=1` flag to mark the shared memory as
read-only and trigger a hardware exception if the code accidentally attempts to modify it.

*****************
Cycle collector
*****************
## Cycle collector

Sometimes, reference counting is not enough. Consider the following example:

.. code:: php
```php
$a = new stdClass;
$b = new stdClass;
$a->b = $b;
$b->a = $a;
unset($a);
unset($b);
```

When this code finishes, the reference count of both instances of ``stdClass`` will still be 1, as
When this code finishes, the reference count of both instances of `stdClass` will still be 1, as
they reference each other. This is called a reference cycle.

PHP implements a cycle collector that detects such cycles and frees values that are only reachable
through their own references. The cycle collector will record values that may be involved in a
cycle, and run when this buffer becomes full. It is also possible to invoke it explicitly by calling
the ``gc_collect_cycles()`` function. The cycle collectors design is described in the `Cycle
collector <todo>`_ chapter.
the `gc_collect_cycles()` function. The cycle collectors design is described in the <a
href="todo">Cycle collector</a> chapter.

**********
GC flags
**********
## GC flags

.. code:: c
```c
/* zval_gc_flags(zval.value->gc.u.type_info) (common flags) */
#define GC_NOT_COLLECTABLE (1<<4)
#define GC_PROTECTED (1<<5) /* used for recursion detection */
#define GC_IMMUTABLE (1<<6) /* can't be changed in place */
#define GC_PERSISTENT (1<<7) /* allocated using malloc */
#define GC_PERSISTENT_LOCAL (1<<8) /* persistent, but thread-local */
```

The ``GC_NOT_COLLECTABLE`` flag indicates that the value may not be involved in a reference cycle.
The `GC_NOT_COLLECTABLE` flag indicates that the value may not be involved in a reference cycle.
This allows for a fast way to detect values that don't need to be added to the cycle collector
buffer. Only arrays and objects may actually be involved in reference cycles.

The ``GC_PROTECTED`` flag is used to protect against recursion in various internal functions. For
example, ``var_dump`` recursively prints the contents of values, and marks visited values with the
``GC_PROTECTED`` flag. If the value is recursive, it prevents the same value from being visited
The `GC_PROTECTED` flag is used to protect against recursion in various internal functions. For
example, `var_dump` recursively prints the contents of values, and marks visited values with the
`GC_PROTECTED` flag. If the value is recursive, it prevents the same value from being visited
again.

``GC_IMMUTABLE`` has been discussed in `Immutable reference counted types`_.
`GC_IMMUTABLE` has been discussed in [Immutable reference counted types](#immutable-reference-counted-types).

The ``GC_PERSISTENT`` flag indicates that the value was allocated using ``malloc``, instead of PHPs
The `GC_PERSISTENT` flag indicates that the value was allocated using `malloc`, instead of PHPs
own allocator. Usually, such values are alive for the entire lifetime of the process, instead of
being freed at the end of the request. See the `Zend allocator <todo>`_ chapter for more
being freed at the end of the request. See the <a href="todo">Zend allocator</a> chapter for more
information.

The ``GC_PERSISTENT_LOCAL`` flag indicates that a ``GC_PERSISTENT`` value is only accessible in one
The `GC_PERSISTENT_LOCAL` flag indicates that a `GC_PERSISTENT` value is only accessible in one
thread, and is thus still safe to modify. This flag is only used in debug builds to satisfy an
``assert``.
`assert`.
Loading
Loading