Работа с WebAssembly и PHP: от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в WebAssembly и PHP

WebAssembly (Wasm) — это низкоуровневый бинарный формат инструкций, который выполняется в виртуальной машине с производительностью, близкой к нативной. В контексте PHP он открывает новые горизонты: вы можете запускать код на C, C++, Rust или Go прямо из PHP-скрипта, не покидая серверную среду. Это особенно полезно для ресурсоемких задач — обработки изображений, криптографии, парсинга данных или математических расчетов.

В этой статье мы разберем полный цикл работы: от компиляции исходного кода в Wasm до вызова функций из PHP с помощью расширений wasmtime и wasmer. Вы узнаете, как повысить производительность PHP-приложений, используя мощь машинного кода.

Основы WebAssembly: что нужно знать PHP-разработчику

Архитектура Wasm

WebAssembly работает в изолированной песочнице (sandbox). Модуль Wasm не имеет прямого доступа к файловой системе, сети или памяти хоста — только к своей линейной памяти и экспортируемым функциям. Это обеспечивает безопасность, но требует явного управления данными.

Ключевые понятия:

  • Модуль — скомпилированный бинарный файл (.wasm)
  • Экспорты — функции и переменные, доступные извне
  • Импорты — функции, которые хост (PHP) предоставляет модулю
  • Линейная память — непрерывный массив байтов, которым оперирует модуль

Инструменты для работы с Wasm в PHP

Существует два основных расширения:

  • Wasmtime — от Bytecode Alliance, самый популярный и стабильный
  • Wasmer — альтернатива с поддержкой множества бэкендов (Cranelift, LLVM, Singlepass)

Установка через Composer:

composer require wasmtime/wasmtime-php
# или
composer require wasmer/wasmer

Также потребуется установить системную библиотеку Wasmtime или Wasmer. Для Ubuntu:

curl https://wasmtime.dev/install.sh -sSf | bash
# или
curl https://get.wasmer.io -sSfL | sh

Компиляция C/C++ в WebAssembly

Для компиляции используем Emscripten — инструментарий на базе LLVM/Clang. Установка:

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

Пример: функция суммирования на C

Создадим файл math.c:

#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
    return a + b;
}

EMSCRIPTEN_KEEPALIVE
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

Компилируем в Wasm:

emcc math.c -o math.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_add","_factorial"]' -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' -O3

Флаг -O3 включает максимальную оптимизацию. Файл math.wasm готов к использованию.

Интеграция Wasm с PHP: пошаговые примеры

Сценарий 1: Использование Wasmtime

Установите расширение и напишите следующий PHP-код:

<?php
require 'vendor/autoload.php';

use Wasmtime\Engine;
use Wasmtime\Module;
use Wasmtime\Store;
use Wasmtime\Func;
use Wasmtime\Val;

// Загружаем модуль
$engine = new Engine();
$module = Module::fromFile($engine, 'math.wasm');
$store = new Store($engine);

// Создаем экземпляр
$instance = new Wasmtime\Instance($store, $module, []);

// Получаем экспортированную функцию
$add = $instance->getExport($store, 'add');
$factorial = $instance->getExport($store, 'factorial');

// Вызываем функции
$resultAdd = $add->call($store, 5, 7);
$resultFact = $factorial->call($store, 10);

echo "5 + 7 = " . $resultAdd[0]->i32() . "\n"; // 12
echo "10! = " . $resultFact[0]->i32() . "\n";  // 3628800
?>

Важно: параметры передаются как отдельные значения, результат возвращается в виде массива объектов Val.

Сценарий 2: Передача строк и сложных данных

Wasm оперирует только числами. Для работы со строками нужно копировать данные в линейную память модуля. Создадим C-функцию для преобразования строки в верхний регистр:

EMSCRIPTEN_KEEPALIVE
void to_upper(char *str, int len) {
    for (int i = 0; i < len; i++) {
        if (str[i] >= 'a' && str[i] <= 'z') {
            str[i] -= 32;
        }
    }
}

PHP-код для работы с памятью:

<?php
$instance = new Wasmtime\Instance($store, $module, []);

$memory = $instance->getExport($store, 'memory');
$toUpper = $instance->getExport($store, 'to_upper');

$input = "hello world";
$len = strlen($input);

// Выделяем память в Wasm (предположим, что функция malloc экспортирована)
$malloc = $instance->getExport($store, 'malloc');
$ptr = $malloc->call($store, $len)[0]->i32();

// Записываем данные в память
$bytes = unpack('C*', $input);
$memory->write($store, $ptr, $bytes);

// Вызываем функцию
$toUpper->call($store, $ptr, $len);

// Читаем результат
$resultBytes = $memory->read($store, $ptr, $len);
$result = implode('', array_map('chr', $resultBytes));

echo $result; // HELLO WORLD

// Освобождаем память
$free = $instance->getExport($store, 'free');
$free->call($store, $ptr);
?>

Сценарий 3: Использование Wasmer (альтернативный подход)

Код с Wasmer аналоги

Блоги

Рекомендаций по книгам