diff --git a/docs/source/index.rst b/docs/source/index.rst index 667f761497be4..53178820892a6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,6 +15,12 @@ core/data-structures/index +.. toctree:: + :caption: Miscellaneous + :hidden: + + miscellaneous/stubs + Welcome to the php-src documentation! .. warning:: diff --git a/docs/source/miscellaneous/stubs.rst b/docs/source/miscellaneous/stubs.rst new file mode 100644 index 0000000000000..5a3f5b02217b0 --- /dev/null +++ b/docs/source/miscellaneous/stubs.rst @@ -0,0 +1,776 @@ +####### + Stubs +####### + +Stub files are pieces of PHP code which only contain declarations. They do not include runnable +code, but instead contain empty function and method bodies. A very basic stub looks like this: + +.. code:: php + + ce_flags |= ZEND_ACC_DEPRECATED|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; + class_entry->doc_comment = zend_string_init_interned("/**\n * This is a comment\n * @see https://www.php.net */", 55, 1); + + ... + + return class_entry; + } + +******************************************** + Generating Global Constants and Attributes +******************************************** + +Although global constants and function attributes do not relate to classes, they require the ``/** +@generate-class-entries */`` file-level PHPDoc block. + +If a global constant or function attribute are present in the stub file, the generated C-code will +include a ``register_{$stub_file_name}_symbols()`` file. + +Given the following file: + +.. code:: php + + // example.stub.php + = 80000) + # include "example_arginfo.h" + #else + # include "example_legacy_arginfo.h" + #endif + +When ``@generate-legacy-arginfo`` is passed the minimum PHP version ID that needs to be supported, +then only one arginfo file is going to be generated, and ``#if`` prepocessor directives will ensure +compatibility with all the required PHP 8 versions. + +PHP Version IDs are as follows: ``80000`` for PHP 8.0, ``80100`` for PHP PHP 8.1, ``80200`` for PHP +8.2, ``80300`` for PHP 8.3, and ``80400`` for PHP 8.4, + +In this example we add a PHP 8.0 compatibility requirement to a slightly modified version of a +previous example: + +.. code:: php + + = ...)`` conditions in the generated arginfo file: + +.. code:: c + + ... + + #if (PHP_VERSION_ID >= 80100) + static zend_class_entry *register_class_Number(void) + { + zend_class_entry *class_entry = zend_register_internal_enum("Number", IS_STRING, class_Number_methods); + + zend_enum_add_case_cstr(class_entry, "One", NULL); + + return class_entry; + } + #endif + + static zend_class_entry *register_class_Elephant(void) + { + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + #if (PHP_VERSION_ID >= 80100) + class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; + #elif (PHP_VERSION_ID >= 80000) + class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; + #endif + + zval const_PI_value; + ZVAL_DOUBLE(&const_PI_value, M_PI); + zend_string *const_PI_name = zend_string_init_interned("PI", sizeof("PI") - 1, 1); + #if (PHP_VERSION_ID >= 80300) + zend_declare_typed_class_constant(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_DOUBLE)); + #else + zend_declare_class_constant_ex(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL); + #endif + zend_string_release(const_PI_name); + + zval property_name_default_value; + ZVAL_UNDEF(&property_name_default_value); + zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); + #if (PHP_VERSION_ID >= 80100) + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + #elif (PHP_VERSION_ID >= 80000) + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + #endif + zend_string_release(property_name_name); + + return class_entry; + } + +The preprocessor conditions are necessary because ``enum``s, ``readonly`` properties, and the +``not-serializable`` flag, are PHP 8.1 features and don't exist in PHP 8.0. + +The registration of ``Number`` is therefore completely omitted, while the ``readonly`` flag is not +added for``Elephpant::$name`` for PHP versions before 8.1. + +Additionally, typed class constants are new in PHP 8.3, and hence a different registration function +is used for versions before 8.3. + +****************************************** + Generating Information for the Optimizer +****************************************** + +A list of functions is maintained for the optimizer in ``Zend/Optimizer/zend_func_infos.h``. This +file contains extra information about the return type and the cardinality of the return value. This +can enable more accurate optimizations (i.e. better type inference). + +Previously, the file was maintained manually, but since PHP 8.1, ``gen_stub.php`` can take care of +this with the ``--generate-optimizer-info`` option. + +This feature is only available for built-in stubs inside php-src, since currently there is no way to +provide the function list for the optimizer other than overwriting ``zend_func_infos.h`` directly. + +A function is added to ``zend_func_infos.h`` if either the ``@return`` or the ``@refcount`` PHPDoc +tag supplies more information than what is available based on the return type declaration. By +default, scalar return types have a ``refcount`` of ``0``, while non-scalar values are ``N``. If a +function can only return newly created non-scalar values, its ``refcount`` can be set to ``1``. + +An example from the built-in functions: + +.. code:: php + + /** + * @return array + * @refcount 1 + */ + function get_declared_classes(): array {} + +Functions can be evaluated at compile-time if their arguments are known in compile-time, and their +behavior is free from side-effects and is not affected by the global state. + +The list of such functions in the optimizer was maintained manually until PHP 8.2. + +Since PHP 8.2, the ``@compile-time-eval`` PHPDoc tag can be applied to any function which conforms +to the above restrictions in order for them to qualify as evaluable at compile-time. The feature +internally works by adding the ``ZEND_ACC_COMPILE_TIME_EVAL`` function flag. + +In PHP 8.4, arity-based frameless functions were introduced. This is another optimization technique, +which results in faster internal function calls by eliminating unnecessary checks for the number of +passed parameters—if the number of passed arguments is known at compile-time. + +To take advantage of frameless functions, add the ``@frameless-function`` PHPDoc tag with some +configuration. + +Since only arity-based optimizations are supported, the tag has the form: ``@frameless-function +{"arity": NUM}``. ``NUM`` is the number of parameters for which a frameless function is available. + +The stub of ``in_array()`` is a good example: + +.. code:: php + + /** + * @compile-time-eval + * @frameless-function {"arity": 2} + * @frameless-function {"arity": 3} + */ + function in_array(mixed $needle, array $haystack, bool $strict = false): bool {} + +Apart from being compile-time evaluable, it has a frameless function counterpart for both the 2 and +the 3-parameter signatures: + +.. code:: c + + /* The regular in_array() function */ + PHP_FUNCTION(in_array) + { + php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + } + + /* The frameless version of the in_array() function when 2 arguments are passed */ + ZEND_FRAMELESS_FUNCTION(in_array, 2) + { + zval *value, *array; + + Z_FLF_PARAM_ZVAL(1, value); + Z_FLF_PARAM_ARRAY(2, array); + + _php_search_array(return_value, value, array, false, 0); + + flf_clean:; + } + + /* The frameless version of the in_array() function when 3 arguments are passed */ + ZEND_FRAMELESS_FUNCTION(in_array, 3) + { + zval *value, *array; + bool strict; + + Z_FLF_PARAM_ZVAL(1, value); + Z_FLF_PARAM_ARRAY(2, array); + Z_FLF_PARAM_BOOL(3, strict); + + _php_search_array(return_value, value, array, strict, 0); + + flf_clean:; + } + +************************************** + Generating Signatures for the Manual +************************************** + +The manual should reflect the exact same signatures which are represented by the stubs. This is not +exactly the case yet for built-in symbols, but ``gen_stub.php`` has multiple features to automate +the process of synchronization. + +Newly added functions or methods can be documented by providing the ``--generate-methodsynopses`` +option. + +Running ``./build/gen_stub.php --generate-methodsynopses ./ext/mbstring +../doc-en/reference/mbstring`` will create a dedicated page for each ``ext/mbstring`` function which +is not yet documented, and saves them into the ``../doc-en/reference/mbstring/functions`` directory. + +Since these are stub documentation pages, many of the sections are empty. Relevant descriptions have +to be added, and irrelevant sections should be removed. + +Functions or methods that are already available in the manual, the documented signatures can be +updated by providing the ``--replace-methodsynopses`` option. + +Running ``./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/`` will update the function or +method signatures in the English documentation whose stub counterpart is found. + +Class signatures can be updated in the manual by providing the ``--replace-classsynopses`` option. + +Running ``./build/gen_stub.php --replace-classsynopses ./ ../doc-en/`` will update all the class +signatures in the English documentation whose stub counterpart is found. + +If a symbol is not intended to be documented, the ``@undocumentable`` PHPDoc tag should be added to +it. Doing so will prevent any documentation to be created for the given symbol. To avoid a whole +stub file to be added to the manual, this PHPDoc tag should be applied to the file itself. + +These flags are useful for symbols which exist only for testing purposes (e.g. the ones declared for +``ext/zend_test``), or by some other reason documentation is not possible. + +************ + Validation +************ + +You can use the ``--verify`` flag to ``gen_stub.php`` to validate whether the alias function/method +signatures are correct. + +An alias function/method should have the exact same signature as its aliased function/method +counterpart, apart from the name. In some cases this is not possible. For example. ``bzwrite()`` is +an alias of ``fwrite()``, but the name of the first parameter is different because the resource +types differ. + +In order to suppress the error when the check is false positive, the ``@no-verify`` PHPDoc tag +should be applied to the alias: + +.. code:: php + + /** + * @param resource $bz + * @implementation-alias fwrite + * @no-verify Uses different parameter name + */ + function bzwrite($bz, string $data, ?int $length = null): int|false {} + +Besides aliases, the contents of the documentation can also be validated by providing the +``--verify-manual`` option to ``gen_stub.php``. This flag requires the directory with the source +stubs, and the target manual directory, as in ``./build/gen_stub.php --verify-manual ./ +../doc-en/``. + +For this validation, all ``php-src`` stubs and the full English documentation should be available by +the specified path. + +This feature performs the following validations: + +- Detecting missing global constants +- Detecting missing classes +- Detecting missing methods +- Detecting incorrectly documented alias functions or methods + +Running it with the stub examples that are used in this guide, the following warnings are shown: + +.. code:: shell + + Warning: Missing class synopsis for Number + Warning: Missing class synopsis for Elephant + Warning: Missing class synopsis for Atmosphere + Warning: Missing method synopsis for fahrenheitToCelcius() + Warning: Missing method synopsis for Atmosphere::calculateBar() + +********************** + Parameter Statistics +********************** + +The ``gen_stub.php`` flag ``--parameter-stats`` counts how many times a parameter name occurs in the +codebase. + +A JSON object is displayed, containing the parameter names and the number of their occurrences in +descending order.