Введение: зачем PHP нужен WebAssembly?
WebAssembly (Wasm) — это низкоуровневый бинарный формат инструкций, который позволяет выполнять код на языках C, C++, Rust и других со скоростью, близкой к нативной. В контексте PHP интеграция с WebAssembly открывает новые возможности: от выполнения вычислительно сложных задач на серверной стороне до переноса существующих библиотек без переписывания их на PHP.
PHP — язык сценариев, который традиционно уступает в производительности компилируемым языкам. Однако с помощью WebAssembly вы можете:
- Выполнять критичные по скорости алгоритмы (обработка изображений, шифрование, парсинг) на Wasm-модулях.
- Использовать существующие C/C++ библиотеки в PHP-приложениях.
- Снизить нагрузку на сервер за счёт оптимизированного бинарного кода.
В этой статье мы рассмотрим, как интегрировать WebAssembly с PHP на практике: от загрузки модуля до передачи данных и обработки результатов.
1. Основы работы с WebAssembly в PHP
Для работы с Wasm в PHP используется расширение wasm (или wasmer для PHP). Установка выполняется через Composer:
composer require wasm/wasm
После установки вы можете загружать и выполнять Wasm-модули. Рассмотрим простейший пример: модуль на C, который суммирует два числа.
Сначала скомпилируем C-код в Wasm:
// sum.c
int add(int a, int b) {
return a + b;
}
Компиляция (требуется Emscripten или wasi-sdk):
emcc sum.c -o sum.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_add"]'
Теперь загрузим модуль в PHP и вызовем функцию:
<?php
use Wasm\Instance;
use Wasm\Module;
// Загружаем бинарный файл
$wasmBytes = file_get_contents('sum.wasm');
$module = new Module($wasmBytes);
$instance = new Instance($module);
// Вызываем экспортированную функцию add
$result = $instance->add(10, 20);
echo "Результат: " . $result; // 30
?>
Код прост и интуитивен: мы создаём экземпляр модуля и вызываем его функции как методы PHP-объекта. Важно помнить, что Wasm-функции работают с примитивными типами (int, float), поэтому для сложных структур данных потребуется дополнительная обработка.
2. Передача сложных данных: строки и массивы
WebAssembly оперирует только числами. Для передачи строк или массивов необходимо выделять память внутри Wasm-модуля и передавать указатели. Рассмотрим пример с обработкой строки.
Создадим модуль на C, который преобразует строку в верхний регистр:
// uppercase.c
#include <string.h>
#include <ctype.h>
void to_upper(char* str, int len) {
for (int i = 0; i < len; i++) {
str[i] = toupper(str[i]);
}
}
Компилируем с экспортом функций работы с памятью:
emcc uppercase.c -o uppercase.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_to_upper", "_malloc", "_free"]' -s ALLOW_MEMORY_GROWTH=1
PHP-код для вызова:
<?php
$wasmBytes = file_get_contents('uppercase.wasm');
$module = new Module($wasmBytes);
$instance = new Instance($module);
$input = "hello webassembly";
$len = strlen($input) + 1; // +1 для нуль-терминатора
// Выделяем память в Wasm-модуле
$ptr = $instance->malloc($len);
// Записываем строку в память модуля
$instance->memory->write($ptr, $input);
// Вызываем функцию преобразования
$instance->to_upper($ptr, $len);
// Читаем результат из памяти
$result = $instance->memory->read($ptr, $len);
echo "Результат: " . trim($result); // HELLO WEBASSEMBLY
// Освобождаем память
$instance->free($ptr);
?>
Этот подход требует ручного управления памятью, но даёт полный контроль над данными. Для упрощения можно использовать библиотеки-обёртки, например wasm-php-bridge.
3. Обработка ошибок и отладка
При работе с Wasm в PHP возможны типичные ошибки:
- Неверный формат модуля — убедитесь, что файл .wasm скомпилирован корректно и не повреждён.
- Ошибки памяти — выход за границы выделенного буфера может вызвать крах модуля. Всегда проверяйте длину строк и размеры массивов.
- Отсутствие экспортируемых функций — проверьте, что функции экспортированы с правильными именами (часто с префиксом
_).
Для отладки используйте встроенные методы Wasm-модуля:
<?php
try {
$instance = new Instance($module);
// Попытка вызвать несуществующую функцию
$instance->nonExistentFunc();
} catch (\Wasm\Exception $e) {
echo "Ошибка Wasm: " . $e->getMessage();
}
?>
Также полезно логировать все вызовы и проверять возвращаемые значения. Если модуль написан на Rust или C, добавьте в него функции для логирования через импорт из окружения (host functions).
4. Практический пример: обработка изображений
Рассмотрим реальный сценарий — изменение размера изображения с помощью библиотеки stb_image на C, скомпилированной в Wasm.
Создадим модуль resize.c:
// resize.c
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_resize.h"
int resize_image(unsigned char* input, int w, int h, int channels,
unsigned char* output, int new_w, int new_h) {
return stbir_resize_uint8(input, w, h, 0, output, new_w, new_h, 0, channels);
}
PHP-код для вызова:
<?php
$wasmBytes = file_get_contents('resize.wasm');
$module = new Module($wasmBytes);
$instance = new Instance($module);
// Загружаем изображение (предположим, что это PNG 100x100)
$imageData = file_get_contents('input.png');
$width = 100