Введение в интеграцию WebAssembly и PHP
WebAssembly (Wasm) — это бинарный формат инструкций, который позволяет выполнять высокопроизводительный код в веб-браузере и на сервере. На продвинутом уровне интеграция WebAssembly с PHP открывает возможности для создания гибридных приложений, где критичные по производительности участки кода (например, обработка изображений, криптография, симуляции) выполняются на Wasm, а вся бизнес-логика остаётся на PHP.
В этой статье мы рассмотрим, как настроить среду, вызывать Wasm-модули из PHP, передавать сложные данные и оптимизировать взаимодействие. Все примеры ориентированы на среду выполнения Wasmtime — наиболее зрелый рантайм для серверного WebAssembly.
1. Подготовка окружения: установка Wasmtime и PHP-расширения
Для работы с WebAssembly в PHP потребуется установить Wasmtime (от Bytecode Alliance) и одноименное PHP-расширение. Расширение предоставляет нативный интерфейс для загрузки, инстанциирования и вызова функций из Wasm-модулей.
Установка Wasmtime
# Для Linux/macOS
curl https://wasmtime.dev/install.sh -sSf | bash
# Для Windows — скачайте установщик с официального сайта
# https://github.com/bytecodealliance/wasmtime/releases
Установка PHP-расширения wasmtime
Расширение можно установить через PECL или собрать из исходников:
pecl install wasmtime
# Проверка установки
php -m | grep wasmtime
После установки добавьте в php.ini строку:
extension=wasmtime.so
Компиляция Wasm-модуля
Для тестов создадим простой модуль на Rust, который экспортирует функцию сложения:
// lib.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
Скомпилируйте в Wasm:
rustc --target wasm32-wasi -O -o add.wasm lib.rs
2. Загрузка и вызов Wasm-модуля из PHP
Теперь создадим PHP-скрипт, который загрузит скомпилированный модуль и вызовет функцию add.
Базовый вызов функции
<?php
// Загружаем Wasm-модуль
$engine = new Wasmtime\Engine();
$module = Wasmtime\Module::new($engine, file_get_contents('add.wasm'));
// Создаем хранилище и инстанс
$store = new Wasmtime\Store($engine);
$instance = new Wasmtime\Instance($store, $module, []);
// Получаем экспортированную функцию
$add = $instance->exports($store)['add'];
// Вызываем функцию
$result = $add($store, 5, 3);
echo "Результат: " . $result; // Выведет 8
?>
Обработка ошибок и типов данных
Wasmtime поддерживает только базовые типы: i32, i64, f32, f64. Для передачи строк и массивов придётся использовать линейную память Wasm. Рассмотрим пример с передачей строки через память:
<?php
// Предположим, в Wasm-модуле есть функция:
// void greet(void* ptr, int len)
$memory = $instance->exports($store)['memory'];
$greet = $instance->exports($store)['greet'];
// Записываем строку в память Wasm
$str = "Привет из PHP!";
$len = strlen($str);
$ptr = $instance->exports($store)['malloc']($store, $len);
$memory->write($store, $ptr, $str);
// Вызываем функцию
$greet($store, $ptr, $len);
// Освобождаем память (если есть free)
$instance->exports($store)['free']($store, $ptr, $len);
?>
3. Оптимизация производительности: кэширование и пулинг инстансов
Создание инстанса Wasm-модуля — дорогая операция. На продвинутом уровне необходимо кэшировать скомпилированные модули и использовать пул инстансов.
Кэширование скомпилированного модуля
<?php
$cachePath = '/tmp/wasm_cache/';
$moduleKey = md5_file('add.wasm');
$cachedFile = $cachePath . $moduleKey . '.wasm';
if (file_exists($cachedFile)) {
$compiled = file_get_contents($cachedFile);
$module = Wasmtime\Module::deserialize($engine, $compiled);
} else {
$module = Wasmtime\Module::new($engine, file_get_contents('add.wasm'));
file_put_contents($cachedFile, $module->serialize());
}
?>
Пул инстансов (пример с SplObjectStorage)
<?php
class WasmPool {
private array $pool = [];
private int $maxSize;
private string $wasmPath;
public function __construct(string $wasmPath, int $maxSize = 10) {
$this->wasmPath = $wasmPath;
$this->maxSize = $maxSize;
}
public function acquire(): Wasmtime\Instance {
if (!empty($this->pool)) {
return array_pop($this->pool);
}
// Создаем новый инстанс
$engine = new Wasmtime\Engine();
$module = Wasmtime\Module::new($engine, file_get_contents($this->wasmPath));
$store = new Wasmtime\Store($engine);
return new Wasmtime\Instance($store, $module, []);
}
public function release(Wasmtime\Instance $instance): void {
if (count($this->pool) < $this->maxSize) {
$this->pool[] = $instance;
}
}
}
// Использование
$pool = new WasmPool('add.wasm');
$instance = $pool->acquire();
// ... работа с инстансом ...
$pool->release($instance);
?>
4. Продвинутые сценарии: передача сложных структур и вызов PHP из Wasm
Настоящая мощь раскрывается при двустороннем взаимодействии. Wasmtime позволяет импортировать в Wasm-модуль функции, реализованные на PHP.
Импорт PHP-функции в Wasm
<?php
// Определяем функцию, которую будет вызывать Wasm
$hostFunc = new Wasmtime\Func($store, [
Wasmtime\ValType::i32(),
Wasmtime\ValType::i32()
], [Wasmtime\ValType::i32()], function($a, $b) {
// Вызываем PHP