Введение в 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 аналоги