Пользовательские оптимизаторы

В большинстве распространенных функций в Zephir используются внутренние оптимизаторы. Оптимизатор работает как перехватчик для вызовов функций. Оптимизатор заменяет вызов функции в пользовательском пространстве PHP прямыми Си-вызовами, которые выполняются быстрее и имеют более низкие накладные расходы, повышающие производительность.

Чтобы создать оптимизатор, вам нужно создать класс в каталоге «optimizers», необходимо использовать следующее соглашение (вы можете настроить название этого каталога в файле config.json; см. ниже). Следующие соглашения по именованию являются обязательными:

Функция в Zephir Название класса оптимизатора Путь оптимизатора Функция в Си
calculate_pi CalculatePiOptimizer optimizers/CalculatePiOptimizer.php my_calculate_pi

Обратите внимание, что оптимизатор написан на языке PHP, а не Zephir. Он используется во время компиляции для программной генерации соответствующего Си-кода для вызова вашего расширения. Оптимизатор ответственен за проверку, что аргументы и возвращаемые типы соответствуют тому, что необходимо Си-функции, помогая компилятору Zephir генерировать корректный Си-код.

Вот базовая структура для оптимизатора:

<?php

namespace Zephir\Optimizers\FunctionCall;

use Zephir\Call;
use Zephir\CompilationContext;
use Zephir\Compiler\CompilerException;
use Zephir\Optimizers\OptimizerAbstract;

class CalculatePiOptimizer extends OptimizerAbstract
{
    public function optimize(array $expression, Call $call, CompilationContext $context)
    {
        //...

    }
}

Реализация оптимизаторов в значительной степени зависит от типа кода, который вы хотите сгенерировать. В нашем примере мы заменим вызов этой функции вызовом Си-функции. В Zephir, код, используемый для вызова этой функции, выглядит так:

let pi = calculate_pi(1000);

Таким образом, оптимизатор будет ожидать только один параметр, нам необходимо проверить это, чтобы избежать проблем позже:

<?php

public function optimize(array $expression, Call $call, CompilationContext $context)
{

    if (!isset($expression['parameters'])) {
        throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    }

    if (count($expression['parameters']) > 1) {
        throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    }

    //...
}

Существуют функции, которые вызываются, но не возвращают никакого значения. Однако наша функция возвращает вычисленное число PI. Поэтому нам нужно проверить, что переменная, используемая для вычисления значения представляет допустимый тип данных:

<?php

public function optimize(array $expression, Call $call, CompilationContext $context)
{

    if (!isset($expression['parameters'])) {
        throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    }

    if (count($expression['parameters']) > 1) {
        throw new CompilerException("'calculate_pi' requires one parameter", $expression);
    }

    /**
     * Обработка возвращаемого символа
     */
    $call->processExpectedReturn($context);

    $symbolVariable = $call->getSymbolVariable();
    if (!$symbolVariable->isDouble()) {
        throw new CompilerException("Calculated PI values only can be stored in double variables", $expression);
    }

    //...
}

Здесь мы проверяем, будет ли возвращенное значение сохранено в переменной типа double; если нет, то компилятором выбрасывается исключение.

Следующее, что нам нужно сделать, это обработать параметры, переданные функции:

<?php

$resolvedParams = $call->getReadOnlyResolvedParams($expression['parameters'], $context, $expression);

Хорошей практикой в Zephir считается создание функций, которые не изменяют параметры. Если вы меняете переданные параметры, Zephir нужно будет выделить для них память, и в этом случае вы должны будете использовать getResolvedParams вместо getReadOnlyResolvedParams.

Код, возвращаемый этими методами, является валидным Си-кодом, который может быть использован при создании кода для генерации вызова Си-функции:

<?php

// Генерируем Си-код
return new CompiledExpression('double', 'calculate_pi( ' . $resolvedParams[0] . ')', $expression);

Все оптимизаторы должны возвращать экземпляр CompiledExpression. Это сообщит компилятору тип возвращенного кода и соответствующий ему Си-код.

Полный код оптимизатора:

<?php

namespace Zephir\Optimizers\FunctionCall;

use Zephir\Call;
use Zephir\CompilationContext;
use Zephir\CompiledExpression;
use Zephir\Compiler\CompilerException;
use Zephir\Optimizers\OptimizerAbstract;

class CalculatePiOptimizer extends OptimizerAbstract
{
    public function optimize(array $expression, Call $call, CompilationContext $context)
    {
        if (!isset($expression['parameters'])) {
            throw new CompilerException("'calculate_pi' requires one parameter", $expression);
        }

        if (count($expression['parameters']) > 1) {
            throw new CompilerException("'calculate_pi' requires one parameter", $expression);
        }

        /**
         * Обработка возвращаемого символа
         */
        $call->processExpectedReturn($context);

        $symbolVariable = $call->getSymbolVariable();
        if (!$symbolVariable->isDouble()) {
            throw new CompilerException("Calculated PI values only can be stored in double variables", $expression);
        }

        $resolvedParams = $call->getReadOnlyResolvedParams($expression['parameters'], $context, $expression);

        return new CompiledExpression('double', 'my_calculate_pi(' . $resolvedParams[0] .  ')', $expression);

Код, реализующий функцию my_calculate_pi написан на Си, и должен быть скомпилирован вместе с расширением.

Этот код должен быть помещен в каталог ext/, в любую поддиректорию, на ваше усмотрение. Однако убедитесь, что имя файла с Си-кодом не конфликтует с файлами, генерируемыми Zephir.

Файл представленный ниже должен содержать Zend Engine заголовки и реализацию Си-функции:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ext.h"

double my_calculate_pi(zval *accuracy) {
    return 0.0;
}

Этот файл должен быть добавлен в специальную секцию файла config.json:

"extra-sources": [
    "utils/pi.c"
]

Наконец, вы должны указать, где Zephir может найти ваш оптимизатор при помощи опции конфигурации optimizer-dirs.

"optimizer-dirs": [
    "optimizers"
]

Check the complete source code of this example here