From d30dfc89bf1b673b2fdc0638766b930adaec228c Mon Sep 17 00:00:00 2001
From: nsfisis index.mjs の名前で保存しておくこと。
import { readFile } from 'node:fs/promises';
-import PHPWasm from './php-wasm.mjs'
-
-const code = await readFile('/dev/stdin', { encoding: 'utf-8' });
-
-const { ccall } = await PHPWasm();
-const result = ccall(
- 'php_wasm_run',
- 'number', ['string'],
- [code],
-);
-console.log(`exit code: ${result}`);
+ import { readFile } from 'node:fs/promises';
+import PHPWasm from './php-wasm.mjs'
+
+const code = await readFile('/dev/stdin', { encoding: 'utf-8' });
+
+const { ccall } = await PHPWasm();
+const result = ccall(
+ 'php_wasm_run',
+ 'number', ['string'],
+ [code],
+);
+console.log(`exit code: ${result}`);
+
標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような php-wasm.mjs (とそこから呼び出される php-wasm.wasm) を作成する。
@@ -137,30 +138,32 @@
先ほどのコードでも使っていたエントリポイントである php_wasm_run を用意する。
#include <stdio.h>
-#include <emscripten.h>
-#include <Zend/zend_execute.h>
-#include <sapi/embed/php_embed.h>
-
-int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
- zend_result result;
-
- int argc = 1;
- char* argv[] = { "php.wasm", NULL };
-
- PHP_EMBED_START_BLOCK(argc, argv);
-
- result = zend_eval_string_ex(code, NULL, "php.wasm code", 1);
-
- PHP_EMBED_END_BLOCK();
-
- fprintf(stdout, "\n");
- fflush(stdout);
- fprintf(stderr, "\n");
- fflush(stderr);
-
- return result == SUCCESS ? 0 : 1;
-}
+ #include <stdio.h>
+#include <emscripten.h>
+#include <Zend/zend_execute.h>
+#include <sapi/embed/php_embed.h>
+
+int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
+ zend_result result;
+
+ int argc = 1;
+ char* argv[] = { "php.wasm", NULL };
+
+ PHP_EMBED_START_BLOCK(argc, argv);
+
+ result = zend_eval_string_ex(code, NULL, "php.wasm code", 1);
+
+ PHP_EMBED_END_BLOCK();
+
+ fprintf(stdout, "\n");
+ fflush(stdout);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+
+ return result == SUCCESS ? 0 : 1;
+}
+ ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。 @@ -185,49 +188,55 @@ まずは Emscripten 公式が提供している Docker イメージを使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
-FROM emscripten/emsdk:3.1.46 AS wasm-builder
+ FROM emscripten/emsdk:3.1.46 AS wasm-builder
+ 次に、php/php-src から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
-RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src
-
-RUN apt-get update && \
- apt-get install -y --no-install-recommends \
- autoconf \
- bison \
- pkg-config \
- re2c \
- && \
- :
+ RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src
+
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ autoconf \
+ bison \
+ pkg-config \
+ re2c \
+ && \
+ :
+ 続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
-RUN cd php-src && \
- ./buildconf --force && \
- emconfigure ./configure \
- --disable-all \
- --disable-mbregex \
- --disable-fiber-asm \
- --disable-cli \
- --disable-cgi \
- --disable-phpdbg \
- --enable-embed=static \
- --enable-mbstring \
- --without-iconv \
- --without-libxml \
- --without-pcre-jit \
- --without-pdo-sqlite \
- --without-sqlite3 \
- && \
- EMCC_CFLAGS='-s ERROR_ON_UNDEFINED_SYMBOLS=0' emmake make -j$(nproc) && \
- mv libs/libphp.a .. && \
- make clean && \
- git clean -fd && \
- :
+ RUN cd php-src && \
+ ./buildconf --force && \
+ emconfigure ./configure \
+ --disable-all \
+ --disable-mbregex \
+ --disable-fiber-asm \
+ --disable-cli \
+ --disable-cgi \
+ --disable-phpdbg \
+ --enable-embed=static \
+ --enable-mbstring \
+ --without-iconv \
+ --without-libxml \
+ --without-pcre-jit \
+ --without-pdo-sqlite \
+ --without-sqlite3 \
+ && \
+ EMCC_CFLAGS='-s ERROR_ON_UNDEFINED_SYMBOLS=0' emmake make -j$(nproc) && \
+ mv libs/libphp.a .. && \
+ make clean && \
+ git clean -fd && \
+ :
+
ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。
@@ -257,22 +266,24 @@
さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。Dockerfile と同じ場所に php-wasm.c という名前で保存し、次のようにする。
COPY php-wasm.c /src/
-
-RUN cd php-src && \
- emcc \
- -c \
- -o php-wasm.o \
- -I . \
- -I TSRM \
- -I Zend \
- -I main \
- ../php-wasm.c \
- && \
- mv php-wasm.o .. && \
- make clean && \
- git clean -fd && \
- :
+ COPY php-wasm.c /src/
+
+RUN cd php-src && \
+ emcc \
+ -c \
+ -o php-wasm.o \
+ -I . \
+ -I TSRM \
+ -I Zend \
+ -I main \
+ ../php-wasm.c \
+ && \
+ mv php-wasm.o .. && \
+ make clean && \
+ git clean -fd && \
+ :
+
emcc は cc (C コンパイラ/リンカ) の Emscripten 版で、-c は「コンパイル」の意。-o や -I は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。
@@ -282,18 +293,20 @@
libphp.a と php-wasm.o が手に入ったので、これらをリンクして WebAssembly のバイナリとそのラッパである JavaScript ファイルを生成する。これにも emcc コマンドを使う。
RUN emcc \
- -s ENVIRONMENT=node \
- -s ERROR_ON_UNDEFINED_SYMBOLS=0 \
- -s EXPORTED_RUNTIME_METHODS='["ccall"]' \
- -s EXPORT_ES6=1 \
- -s INITIAL_MEMORY=16777216 \
- -s INVOKE_RUN=0 \
- -s MODULARIZE=1 \
- -o php-wasm.js \
- php-wasm.o \
- libphp.a \
- ;
+ RUN emcc \
+ -s ENVIRONMENT=node \
+ -s ERROR_ON_UNDEFINED_SYMBOLS=0 \
+ -s EXPORTED_RUNTIME_METHODS='["ccall"]' \
+ -s EXPORT_ES6=1 \
+ -s INITIAL_MEMORY=16777216 \
+ -s INVOKE_RUN=0 \
+ -s MODULARIZE=1 \
+ -o php-wasm.js \
+ php-wasm.o \
+ libphp.a \
+ ;
+
それぞれのフラグについて解説する。
@@ -335,14 +348,16 @@
といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。先ほど掲載した JavaScript のコードは、Dockerfile と同じディレクトリに index.mjs で配置すること。
FROM node:20.7
-
-WORKDIR /app
-COPY --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs
-COPY --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm
-COPY index.mjs /app/
-
-ENTRYPOINT ["node", "index.mjs"]
+ FROM node:20.7
+
+WORKDIR /app
+COPY --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs
+COPY --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm
+COPY index.mjs /app/
+
+ENTRYPOINT ["node", "index.mjs"]
+ Dockerfile、php-wasm.c、index.mjs を用意したら、Docker コンテナをビルドして実行する。
- $ docker build -t php-wasm .
-$ echo 'echo "Hello, World!", PHP_EOL;' | docker run --rm -i php-wasm
-Hello, World!
-
-
-exit code: 0
+ $ docker build -t php-wasm .
+$ echo 'echo "Hello, World!", PHP_EOL;' | docker run --rm -i php-wasm
+Hello, World!
+
+
+exit code: 0
+