summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--vhosts/blog/.editorconfig10
-rw-r--r--vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.ndoc334
-rw-r--r--vhosts/blog/nuldoc-src/config.ts1
-rw-r--r--vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html396
-rw-r--r--vhosts/blog/public/posts/index.html15
-rw-r--r--vhosts/blog/public/tags/index.html7
-rw-r--r--vhosts/blog/public/tags/php/index.html15
-rw-r--r--vhosts/blog/public/tags/wasm/index.html60
8 files changed, 838 insertions, 0 deletions
diff --git a/vhosts/blog/.editorconfig b/vhosts/blog/.editorconfig
new file mode 100644
index 00000000..f6e2ae3e
--- /dev/null
+++ b/vhosts/blog/.editorconfig
@@ -0,0 +1,10 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+
+[*.ndoc]
+indent_style = space
+indent_size = 2
diff --git a/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.ndoc b/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.ndoc
new file mode 100644
index 00000000..e830bfe5
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.ndoc
@@ -0,0 +1,334 @@
+---
+[article]
+title = "【PHP】 PHP の処理系を Emscripten で WebAssembly にコンパイルする"
+description = "PHP の処理系 (php/php-src) を Emscripten で WebAssembly にコンパイルし、任意のコードを隔離された環境で評価できるようにした。"
+tags = [
+ "php",
+ "wasm",
+]
+
+[[article.revisions]]
+date = "2023-10-02"
+remark = "公開"
+---
+<article>
+ <section id="intro">
+ <h>はじめに</h>
+ <p>
+ <a href="https://emscripten.org/">Emscripten</a> を用いて <a href="https://github.com/php/php-src">PHP の処理系</a>を <a href="https://developer.mozilla.org/docs/WebAssembly">WebAssembly</a> にコンパイルした。機能をある程度絞ることで、思ったよりも簡単に実現できたので、備忘録として記しておく。
+ </p>
+ <p>
+ なお、この記事では Emscripten や WebAssembly とは何か知っていることを前提とする。
+ </p>
+ </section>
+ <section id="version">
+ <h>バージョン情報</h>
+ <p>
+ この記事中で使用するソフトウェア等のバージョンを記載する。
+ </p>
+ <ul>
+ <li>Ubuntu 22.04 on WSL2</li>
+ <li>Docker version 24.0.6</li>
+ <li>Emscripten 3.1.46</li>
+ <li>Node.js 20.7.0</li>
+ <li>PHP 8.2.10</li>
+ </ul>
+ <p>
+ なお、Docker から下は Docker 上で導入するので、ホストマシンにはインストールしなくてよい。
+ </p>
+ </section>
+ <section id="goal">
+ <h>本記事のゴール</h>
+ <p>
+ 先にこの記事のゴールを示しておく。これから示す手順のとおりに進めると、次のようなコードが動くようになる。
+ このコードはこのあと使うので、<code>index.mjs</code> の名前で保存しておくこと。
+ </p>
+ <codeblock language="javascript">
+ <![CDATA[
+ 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}`);
+ ]]>
+ </codeblock>
+ <p>
+ 標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような <code>php-wasm.mjs</code> (とそこから呼び出される <code>php-wasm.wasm</code>) を作成する。
+ </p>
+ </section>
+ <section id="build">
+ <h>ビルド</h>
+ <section id="build--write-c-entrypoint">
+ <h>C のエントリポイントを書く</h>
+ <p>
+ 先ほどのコードでも使っていたエントリポイントである <code>php_wasm_run</code> を用意する。
+ </p>
+ <codeblock language="c">
+ <![CDATA[
+ #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;
+ }
+ ]]>
+ </codeblock>
+ <p>
+ ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。
+ </p>
+ <p>
+ まずは <code>EMSCRIPTEN_KEEPALIVE</code> について。
+ これは Emscripten が用意している特殊なマクロである。
+ このマクロが付与されている関数は、どこからも使用されていなくともコンパイル後の WebAssembly バイナリから削除されない。
+ もしこれを付け忘れると、未使用の関数とみなされ削除される。
+ </p>
+ <p>
+ 次に、コードを評価したあとに呼んでいる標準出力と標準エラー出力に対する改行の出力について。
+ 出力バッファから出力させるためだけなら改行を出力させなくとも <code>fflush()</code> だけで事足りると考えたのだが、ないと動かなかったので追加した。
+ これにより、PHP コードの出力の後ろに余分な改行が追加されてしまう。
+ 改行を出力せずともバッファを消費させる手段をご存知のかたはご教示願いたい。
+ </p>
+ </section>
+ <section id="build--compile-to-wasm">
+ <h>WebAssembly にコンパイルする</h>
+ <p>
+ それでは WebAssembly にコンパイルしていこう。ここからは <code>Dockerfile</code> 上のコマンドとして操作を示す。
+ </p>
+ <p>
+ まずは <a href="https://hub.docker.com/r/emscripten/emsdk">Emscripten 公式が提供している Docker イメージ</a>を使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
+ </p>
+ <codeblock language="dockerfile">
+ <![CDATA[
+ FROM emscripten/emsdk:3.1.46 AS wasm-builder
+ ]]>
+ </codeblock>
+ <p>
+ 次に、<a href="https://github.com/php/php-src">php/php-src</a> から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。
+ 有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
+ </p>
+ <codeblock language="dockerfile">
+ <![CDATA[
+ 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 \
+ && \
+ :
+ ]]>
+ </codeblock>
+ <p>
+ 続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
+ </p>
+ <codeblock language="dockerfile">
+ <![CDATA[
+ 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 && \
+ :
+ ]]>
+ </codeblock>
+ <p>
+ ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。
+ </p>
+ <p>
+ まず、<code>buildconf</code> は PHP 処理系をビルドするときに (Emscripten とは関係なく) 使うツールである。
+ このツールの最も重要な仕事は、<code>configure</code> の生成である。
+ </p>
+ <p>
+ 次に <code>configure</code> するわけだが、ここで <code>emconfigure</code> を使う。
+ これを使うことで、Emscripten が上手く諸々のツールチェインを WebAssembly のビルド向けに調整しながら <code>configure</code> してくれる。
+ </p>
+ <p>
+ <code>configure</code> の後ろに指定してあるフラグは、通常の PHP 処理系のビルドで使う <code>configure</code> と同じなので、詳しくはそちらの <code>cofigure --help</code> を参照していただきたい。
+ ほとんどは、機能の無効化のために指定している (依存するライブラリを減らし、ビルドをより簡単にするため)。
+ </p>
+ <p>
+ 通常の C のビルドなら、<code>configure</code> の次は <code>make</code> するところだが、ここでも <code>emmake</code> を使う。
+ 役割はほとんど <code>emconfigure</code> と同様である。
+ 指定してある <code>EMCC_CFLAGS</code> という環境変数は、Emscripten の C コンパイラへのフラグで、ここでは <code>ERROR_ON_UNDEFINED_SYMBOLS</code> を無効化している。
+ これにより、コンパイル中に出現した解決できなかったシンボルを無視するようになる (代わりに、そのシンボルを呼ぼうとしたタイミングで実行時エラーになる)。
+ すべての依存を完全に解決するのは面倒なので、あまり使わない機能については無視してもよいだろう。
+ </p>
+ <p>
+ ここまでを実行すると <code>libs/libphp.a</code> が生成される。これは後で使うので移動させている。
+ </p>
+ <p>
+ さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。
+ <code>Dockerfile</code> と同じ場所に <code>php-wasm.c</code> という名前で保存し、次のようにする。
+ </p>
+ <codeblock language="dockerfile">
+ <![CDATA[
+ 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 && \
+ :
+ ]]>
+ </codeblock>
+ <p>
+ <code>emcc</code> は <code>cc</code> (C コンパイラ/リンカ) の Emscripten 版で、<code>-c</code> は「コンパイル」の意。
+ <code>-o</code> や <code>-I</code> は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。
+ </p>
+ <p>
+ <code>libphp.a</code> と <code>php-wasm.o</code> が手に入ったので、これらをリンクして WebAssembly のバイナリとそのラッパである JavaScript ファイルを生成する。
+ これにも <code>emcc</code> コマンドを使う。
+ </p>
+ <codeblock language="dockerfile">
+ <![CDATA[
+ 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 \
+ ;
+ ]]>
+ </codeblock>
+ <p>
+ それぞれのフラグについて解説する。
+ </p>
+ <p>
+ <code>-s ENVIRONMENT=node</code> は、生成する WebAssembly/JavaScript の実行環境を指定する。
+ 今回は <code>node</code> を指定しているので、Node.js 向けのファイルが生成される。
+ </p>
+ <p>
+ <code>-s ERROR_ON_UNDEFINED_SYMBOLS=0</code> についてはすでに述べたので省略する。
+ </p>
+ <p>
+ <code>-s EXPORTED_RUNTIME_METHODS='["ccall"]'</code> は、生成される JavaScript から公開される API である。
+ すでに <code>index.mjs</code> で使用しているが、<code>ccall('関数名', '返り値の型', ['仮引数の型', ...], ['実引数', ...])</code> のように使う。
+ </p>
+ <p>
+ <code>-s EXPORT_ES6=1</code> は、JavaScript コードを ECMAScript 6 に準拠した module として生成する。
+ これを指定することで、<code>require()</code> ではなく <code>import</code> できる JavaScript を生成させられる。
+ </p>
+ <p>
+ <code>-s INITIAL_MEMORY=16777216</code> は呼んで字のごとく。用途に合わせて適当に決めてほしい。
+ </p>
+ <p>
+ <code>-s INVOKE_RUN=0</code> は、module をロードしたときに勝手に <code>main()</code> を呼ぶかどうか (だと思う)。
+ 今回は <code>php_wasm_run()</code> しか使うつもりがないので切っている。
+ </p>
+ <p>
+ <code>-s MODULARIZE=1</code> は、実質的にほぼ必須のオプションであり、1 を指定することで「WebAssembly module をインスタンス化する関数」をエクスポートするような JavaScript ファイルを生成するようになる。
+ これを指定しないと、生成物の JavaScript ファイルを読み込むと WebAssembly module が即座にインスタンス化されてしまい、起動のタイミングを制御できない。
+ </p>
+ <p>
+ ここまで実行すると、<code>php-wasm.js</code> と <code>php-wasm.wasm</code> が作られる。
+ では、ここからはこれらの実行環境を作っていこう。
+ </p>
+ <p>
+ といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。
+ 先ほど掲載した JavaScript のコードは、<code>Dockerfile</code> と同じディレクトリに <code>index.mjs</code> で配置すること。
+ </p>
+ <codeblock language="dockerfile">
+ <![CDATA[
+ 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"]
+ ]]>
+ </codeblock>
+ </section>
+ </section>
+ <section id="run">
+ <h>実行</h>
+ <p>
+ <code>Dockerfile</code>、<code>php-wasm.c</code>、<code>index.mjs</code> を用意したら、Docker コンテナをビルドして実行する。
+ </p>
+ <codeblock language="dockerfile">
+ <![CDATA[
+ $ docker build -t php-wasm .
+ $ echo 'echo "Hello, World!", PHP_EOL;' | docker run --rm -i php-wasm
+ Hello, World!
+
+
+ exit code: 0
+ ]]>
+ </codeblock>
+ </section>
+ <section id="outro">
+ <h>まとめ</h>
+ <p>
+ <a href="https://github.com/nsfisis/tiny-php.wasm">ここまでをまとめた Git リポジトリ</a>を用意した。
+ 簡単にコンパイルできるので、興味があれば試してみてほしい。
+ </p>
+ </section>
+ <section id="references">
+ <h>参考リンク</h>
+ <ul>
+ <li><a href="https://github.com/php/php-src">php/php-src: ビルドの方法について</a></li>
+ <li><a href="https://emscripten.org/docs/getting_started/Tutorial.html">Emscripten: チュートリアル</a></li>
+ <li><a href="https://emscripten.org/docs/compiling/Building-Projects.html#building-projects">Emscripten: ビルドの基本</a></li>
+ <li><a href="https://emscripten.org/docs/tools_reference/emcc.html#emccdoc">Emscripten: <code>emcc</code> などのリファレンス</a></li>
+ <li><a href="https://emscripten.org/docs/api_reference/module.html#module">Emscripten: 生成される JavaScript の API</a></li>
+ </ul>
+ </section>
+</article>
diff --git a/vhosts/blog/nuldoc-src/config.ts b/vhosts/blog/nuldoc-src/config.ts
index 26f316ca..6e66b188 100644
--- a/vhosts/blog/nuldoc-src/config.ts
+++ b/vhosts/blog/nuldoc-src/config.ts
@@ -29,6 +29,7 @@ export const config = {
"ruby3": "Ruby 3",
"rust": "Rust",
"vim": "Vim",
+ "wasm": "WebAssembly",
},
},
};
diff --git a/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html b/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html
new file mode 100644
index 00000000..966fb452
--- /dev/null
+++ b/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html
@@ -0,0 +1,396 @@
+<!DOCTYPE html>
+<html lang="ja-JP">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="author" content="nsfisis">
+ <meta name="copyright" content="&copy; 2023 nsfisis">
+ <meta name="description" content="PHP の処理系 (php/php-src) を Emscripten で WebAssembly にコンパイルし、任意のコードを隔離された環境で評価できるようにした。">
+ <meta name="keywords" content="PHP,WebAssembly">
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+ <title>【PHP】 PHP の処理系を Emscripten で WebAssembly にコンパイルする | REPL: Rest-Eat-Program Loop</title>
+ <link rel="stylesheet" href="/style.css?h=37fff6a2f0eef473abde58e55f28ea69">
+ <link rel="stylesheet" href="/hl.css?h=340e65ffd5c17713efc9107c06304f7b">
+ </head>
+ <body class="single">
+ <header class="header">
+ <nav class="nav">
+ <ul>
+ <li>
+ <a href="/">REPL: Rest-Eat-Program Loop</a>
+ </li>
+ <li>
+ <a href="/about/">About</a>
+ </li>
+ <li>
+ <a href="/posts/">Posts</a>
+ </li>
+ <li>
+ <a href="/slides/">Slides</a>
+ </li>
+ <li>
+ <a href="/tags/">Tags</a>
+ </li>
+ </ul>
+ </nav>
+ </header>
+ <main class="main">
+ <article class="post-single">
+ <header class="post-header">
+ <h1 class="post-title">【PHP】 PHP の処理系を Emscripten で WebAssembly にコンパイルする</h1>
+ <ul class="post-tags">
+ <li class="tag">
+ <a href="/tags/php/">PHP</a>
+ </li>
+ <li class="tag">
+ <a href="/tags/wasm/">WebAssembly</a>
+ </li>
+ </ul>
+ </header>
+ <div class="post-content">
+ <section>
+ <h2 id="changelog">更新履歴</h2>
+ <ol>
+ <li class="revision">
+ <time datetime="2023-10-02">2023-10-02</time>: 公開
+ </li>
+ </ol>
+ </section>
+ <section id="section--intro">
+ <h2><a href="#section--intro">はじめに</a></h2>
+ <p>
+ <a href="https://emscripten.org/">Emscripten</a> を用いて <a href="https://github.com/php/php-src">PHP の処理系</a>を <a href="https://developer.mozilla.org/docs/WebAssembly">WebAssembly</a> にコンパイルした。機能をある程度絞ることで、思ったよりも簡単に実現できたので、備忘録として記しておく。
+ </p>
+
+ <p>
+ なお、この記事では Emscripten や WebAssembly とは何か知っていることを前提とする。
+ </p>
+ </section>
+
+ <section id="section--version">
+ <h2><a href="#section--version">バージョン情報</a></h2>
+ <p>
+ この記事中で使用するソフトウェア等のバージョンを記載する。
+ </p>
+
+ <ul>
+ <li>
+ Ubuntu 22.04 on WSL2
+ </li>
+
+ <li>
+ Docker version 24.0.6
+ </li>
+
+ <li>
+ Emscripten 3.1.46
+ </li>
+
+ <li>
+ Node.js 20.7.0
+ </li>
+
+ <li>
+ PHP 8.2.10
+ </li>
+ </ul>
+
+ <p>
+ なお、Docker から下は Docker 上で導入するので、ホストマシンにはインストールしなくてよい。
+ </p>
+ </section>
+
+ <section id="section--goal">
+ <h2><a href="#section--goal">本記事のゴール</a></h2>
+ <p>
+ 先にこの記事のゴールを示しておく。これから示す手順のとおりに進めると、次のようなコードが動くようになる。 このコードはこのあと使うので、<code>index.mjs</code> の名前で保存しておくこと。
+ </p>
+
+ <pre class="highlight" language="javascript"><code class="highlight"><span class="hljs-keyword">import</span> { readFile } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;node:fs/promises&#x27;</span>;
+<span class="hljs-keyword">import</span> <span class="hljs-title class_">PHPWasm</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./php-wasm.mjs&#x27;</span>
+
+<span class="hljs-keyword">const</span> code = <span class="hljs-keyword">await</span> <span class="hljs-title function_">readFile</span>(<span class="hljs-string">&#x27;/dev/stdin&#x27;</span>, { <span class="hljs-attr">encoding</span>: <span class="hljs-string">&#x27;utf-8&#x27;</span> });
+
+<span class="hljs-keyword">const</span> { ccall } = <span class="hljs-keyword">await</span> <span class="hljs-title class_">PHPWasm</span>();
+<span class="hljs-keyword">const</span> result = <span class="hljs-title function_">ccall</span>(
+ <span class="hljs-string">&#x27;php_wasm_run&#x27;</span>,
+ <span class="hljs-string">&#x27;number&#x27;</span>, [<span class="hljs-string">&#x27;string&#x27;</span>],
+ [code],
+);
+<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`exit code: <span class="hljs-subst">${result}</span>`</span>);</code></pre>
+
+ <p>
+ 標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような <code>php-wasm.mjs</code> (とそこから呼び出される <code>php-wasm.wasm</code>) を作成する。
+ </p>
+ </section>
+
+ <section id="section--build">
+ <h2><a href="#section--build">ビルド</a></h2>
+ <section id="section--build--write-c-entrypoint">
+ <h3><a href="#section--build--write-c-entrypoint">C のエントリポイントを書く</a></h3>
+ <p>
+ 先ほどのコードでも使っていたエントリポイントである <code>php_wasm_run</code> を用意する。
+ </p>
+
+ <pre class="highlight" language="c"><code class="highlight"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;stdio.h&gt;</span></span>
+<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;emscripten.h&gt;</span></span>
+<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;Zend/zend_execute.h&gt;</span></span>
+<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;sapi/embed/php_embed.h&gt;</span></span>
+
+<span class="hljs-type">int</span> EMSCRIPTEN_KEEPALIVE <span class="hljs-title function_">php_wasm_run</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* code)</span> {
+ zend_result result;
+
+ <span class="hljs-type">int</span> argc = <span class="hljs-number">1</span>;
+ <span class="hljs-type">char</span>* argv[] = { <span class="hljs-string">&quot;php.wasm&quot;</span>, <span class="hljs-literal">NULL</span> };
+
+ PHP_EMBED_START_BLOCK(argc, argv);
+
+ result = zend_eval_string_ex(code, <span class="hljs-literal">NULL</span>, <span class="hljs-string">&quot;php.wasm code&quot;</span>, <span class="hljs-number">1</span>);
+
+ PHP_EMBED_END_BLOCK();
+
+ <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stdout</span>, <span class="hljs-string">&quot;\n&quot;</span>);
+ fflush(<span class="hljs-built_in">stdout</span>);
+ <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">&quot;\n&quot;</span>);
+ fflush(<span class="hljs-built_in">stderr</span>);
+
+ <span class="hljs-keyword">return</span> result == SUCCESS ? <span class="hljs-number">0</span> : <span class="hljs-number">1</span>;
+}</code></pre>
+
+ <p>
+ ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。
+ </p>
+
+ <p>
+ まずは <code>EMSCRIPTEN_KEEPALIVE</code> について。 これは Emscripten が用意している特殊なマクロである。 このマクロが付与されている関数は、どこからも使用されていなくともコンパイル後の WebAssembly バイナリから削除されない。 もしこれを付け忘れると、未使用の関数とみなされ削除される。
+ </p>
+
+ <p>
+ 次に、コードを評価したあとに呼んでいる標準出力と標準エラー出力に対する改行の出力について。 出力バッファから出力させるためだけなら改行を出力させなくとも <code>fflush()</code> だけで事足りると考えたのだが、ないと動かなかったので追加した。 これにより、PHP コードの出力の後ろに余分な改行が追加されてしまう。 改行を出力せずともバッファを消費させる手段をご存知のかたはご教示願いたい。
+ </p>
+ </section>
+
+ <section id="section--build--compile-to-wasm">
+ <h3><a href="#section--build--compile-to-wasm">WebAssembly にコンパイルする</a></h3>
+ <p>
+ それでは WebAssembly にコンパイルしていこう。ここからは <code>Dockerfile</code> 上のコマンドとして操作を示す。
+ </p>
+
+ <p>
+ まずは <a href="https://hub.docker.com/r/emscripten/emsdk">Emscripten 公式が提供している Docker イメージ</a>を使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
+ </p>
+
+ <pre class="highlight" language="dockerfile"><code class="highlight"><span class="hljs-keyword">FROM</span> emscripten/emsdk:<span class="hljs-number">3.1</span>.<span class="hljs-number">46</span> AS wasm-builder</code></pre>
+
+ <p>
+ 次に、<a href="https://github.com/php/php-src">php/php-src</a> から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。 有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
+ </p>
+
+ <pre class="highlight" language="dockerfile"><code class="highlight"><span class="hljs-keyword">RUN</span><span class="language-bash"> git <span class="hljs-built_in">clone</span> --depth=1 --branch=php-8.2.10 https://github.com/php/php-src</span>
+
+<span class="hljs-keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; \
+ apt-get install -y --no-install-recommends \
+ autoconf \
+ bison \
+ pkg-config \
+ re2c \
+ &amp;&amp; \
+ :</span></code></pre>
+
+ <p>
+ 続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
+ </p>
+
+ <pre class="highlight" language="dockerfile"><code class="highlight"><span class="hljs-keyword">RUN</span><span class="language-bash"> <span class="hljs-built_in">cd</span> php-src &amp;&amp; \
+ ./buildconf --force &amp;&amp; \
+ 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 \
+ &amp;&amp; \
+ EMCC_CFLAGS=<span class="hljs-string">&#x27;-s ERROR_ON_UNDEFINED_SYMBOLS=0&#x27;</span> emmake make -j$(<span class="hljs-built_in">nproc</span>) &amp;&amp; \
+ <span class="hljs-built_in">mv</span> libs/libphp.a .. &amp;&amp; \
+ make clean &amp;&amp; \
+ git clean -fd &amp;&amp; \
+ :</span></code></pre>
+
+ <p>
+ ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。
+ </p>
+
+ <p>
+ まず、<code>buildconf</code> は PHP 処理系をビルドするときに (Emscripten とは関係なく) 使うツールである。 このツールの最も重要な仕事は、<code>configure</code> の生成である。
+ </p>
+
+ <p>
+ 次に <code>configure</code> するわけだが、ここで <code>emconfigure</code> を使う。 これを使うことで、Emscripten が上手く諸々のツールチェインを WebAssembly のビルド向けに調整しながら <code>configure</code> してくれる。
+ </p>
+
+ <p>
+ <code>configure</code> の後ろに指定してあるフラグは、通常の PHP 処理系のビルドで使う <code>configure</code> と同じなので、詳しくはそちらの <code>cofigure --help</code> を参照していただきたい。 ほとんどは、機能の無効化のために指定している (依存するライブラリを減らし、ビルドをより簡単にするため)。
+ </p>
+
+ <p>
+ 通常の C のビルドなら、<code>configure</code> の次は <code>make</code> するところだが、ここでも <code>emmake</code> を使う。 役割はほとんど <code>emconfigure</code> と同様である。 指定してある <code>EMCC_CFLAGS</code> という環境変数は、Emscripten の C コンパイラへのフラグで、ここでは <code>ERROR_ON_UNDEFINED_SYMBOLS</code> を無効化している。 これにより、コンパイル中に出現した解決できなかったシンボルを無視するようになる (代わりに、そのシンボルを呼ぼうとしたタイミングで実行時エラーになる)。 すべての依存を完全に解決するのは面倒なので、あまり使わない機能については無視してもよいだろう。
+ </p>
+
+ <p>
+ ここまでを実行すると <code>libs/libphp.a</code> が生成される。これは後で使うので移動させている。
+ </p>
+
+ <p>
+ さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。 <code>Dockerfile</code> と同じ場所に <code>php-wasm.c</code> という名前で保存し、次のようにする。
+ </p>
+
+ <pre class="highlight" language="dockerfile"><code class="highlight"><span class="hljs-keyword">COPY</span><span class="language-bash"> php-wasm.c /src/</span>
+
+<span class="hljs-keyword">RUN</span><span class="language-bash"> <span class="hljs-built_in">cd</span> php-src &amp;&amp; \
+ emcc \
+ -c \
+ -o php-wasm.o \
+ -I . \
+ -I TSRM \
+ -I Zend \
+ -I main \
+ ../php-wasm.c \
+ &amp;&amp; \
+ <span class="hljs-built_in">mv</span> php-wasm.o .. &amp;&amp; \
+ make clean &amp;&amp; \
+ git clean -fd &amp;&amp; \
+ :</span></code></pre>
+
+ <p>
+ <code>emcc</code> は <code>cc</code> (C コンパイラ/リンカ) の Emscripten 版で、<code>-c</code> は「コンパイル」の意。 <code>-o</code> や <code>-I</code> は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。
+ </p>
+
+ <p>
+ <code>libphp.a</code> と <code>php-wasm.o</code> が手に入ったので、これらをリンクして WebAssembly のバイナリとそのラッパである JavaScript ファイルを生成する。 これにも <code>emcc</code> コマンドを使う。
+ </p>
+
+ <pre class="highlight" language="dockerfile"><code class="highlight"><span class="hljs-keyword">RUN</span><span class="language-bash"> emcc \
+ -s ENVIRONMENT=node \
+ -s ERROR_ON_UNDEFINED_SYMBOLS=0 \
+ -s EXPORTED_RUNTIME_METHODS=<span class="hljs-string">&#x27;[&quot;ccall&quot;]&#x27;</span> \
+ -s EXPORT_ES6=1 \
+ -s INITIAL_MEMORY=16777216 \
+ -s INVOKE_RUN=0 \
+ -s MODULARIZE=1 \
+ -o php-wasm.js \
+ php-wasm.o \
+ libphp.a \
+ ;</span></code></pre>
+
+ <p>
+ それぞれのフラグについて解説する。
+ </p>
+
+ <p>
+ <code>-s ENVIRONMENT=node</code> は、生成する WebAssembly/JavaScript の実行環境を指定する。 今回は <code>node</code> を指定しているので、Node.js 向けのファイルが生成される。
+ </p>
+
+ <p>
+ <code>-s ERROR_ON_UNDEFINED_SYMBOLS=0</code> についてはすでに述べたので省略する。
+ </p>
+
+ <p>
+ <code>-s EXPORTED_RUNTIME_METHODS=&apos;[&quot;ccall&quot;]&apos;</code> は、生成される JavaScript から公開される API である。 すでに <code>index.mjs</code> で使用しているが、<code>ccall(&apos;関数名&apos;, &apos;返り値の型&apos;, [&apos;仮引数の型&apos;, ...], [&apos;実引数&apos;, ...])</code> のように使う。
+ </p>
+
+ <p>
+ <code>-s EXPORT_ES6=1</code> は、JavaScript コードを ECMAScript 6 に準拠した module として生成する。 これを指定することで、<code>require()</code> ではなく <code>import</code> できる JavaScript を生成させられる。
+ </p>
+
+ <p>
+ <code>-s INITIAL_MEMORY=16777216</code> は呼んで字のごとく。用途に合わせて適当に決めてほしい。
+ </p>
+
+ <p>
+ <code>-s INVOKE_RUN=0</code> は、module をロードしたときに勝手に <code>main()</code> を呼ぶかどうか (だと思う)。 今回は <code>php_wasm_run()</code> しか使うつもりがないので切っている。
+ </p>
+
+ <p>
+ <code>-s MODULARIZE=1</code> は、実質的にほぼ必須のオプションであり、1 を指定することで「WebAssembly module をインスタンス化する関数」をエクスポートするような JavaScript ファイルを生成するようになる。 これを指定しないと、生成物の JavaScript ファイルを読み込むと WebAssembly module が即座にインスタンス化されてしまい、起動のタイミングを制御できない。
+ </p>
+
+ <p>
+ ここまで実行すると、<code>php-wasm.js</code> と <code>php-wasm.wasm</code> が作られる。 では、ここからはこれらの実行環境を作っていこう。
+ </p>
+
+ <p>
+ といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。 先ほど掲載した JavaScript のコードは、<code>Dockerfile</code> と同じディレクトリに <code>index.mjs</code> で配置すること。
+ </p>
+
+ <pre class="highlight" language="dockerfile"><code class="highlight"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">20.7</span>
+
+<span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>
+<span class="hljs-keyword">COPY</span><span class="language-bash"> --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs</span>
+<span class="hljs-keyword">COPY</span><span class="language-bash"> --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm</span>
+<span class="hljs-keyword">COPY</span><span class="language-bash"> index.mjs /app/</span>
+
+<span class="hljs-keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="hljs-string">&quot;node&quot;</span>, <span class="hljs-string">&quot;index.mjs&quot;</span>]</span></code></pre>
+ </section>
+ </section>
+
+ <section id="section--run">
+ <h2><a href="#section--run">実行</a></h2>
+ <p>
+ <code>Dockerfile</code>、<code>php-wasm.c</code>、<code>index.mjs</code> を用意したら、Docker コンテナをビルドして実行する。
+ </p>
+
+ <pre class="highlight" language="dockerfile"><code class="highlight">$ docker build -t php-wasm .
+$ echo <span class="hljs-string">&#x27;echo &quot;Hello, World!&quot;, PHP_EOL;&#x27;</span> | docker <span class="hljs-keyword">run</span><span class="language-bash"> --<span class="hljs-built_in">rm</span> -i php-wasm</span>
+Hello, World!
+
+
+exit code: <span class="hljs-number">0</span></code></pre>
+ </section>
+
+ <section id="section--outro">
+ <h2><a href="#section--outro">まとめ</a></h2>
+ <p>
+ <a href="https://github.com/nsfisis/tiny-php.wasm">ここまでをまとめた Git リポジトリ</a>を用意した。 簡単にコンパイルできるので、興味があれば試してみてほしい。
+ </p>
+ </section>
+
+ <section id="section--references">
+ <h2><a href="#section--references">参考リンク</a></h2>
+ <ul>
+ <li>
+ <a href="https://github.com/php/php-src">php/php-src: ビルドの方法について</a>
+ </li>
+
+ <li>
+ <a href="https://emscripten.org/docs/getting_started/Tutorial.html">Emscripten: チュートリアル</a>
+ </li>
+
+ <li>
+ <a href="https://emscripten.org/docs/compiling/Building-Projects.html#building-projects">Emscripten: ビルドの基本</a>
+ </li>
+
+ <li>
+ <a href="https://emscripten.org/docs/tools_reference/emcc.html#emccdoc">Emscripten: <code>emcc</code> などのリファレンス</a>
+ </li>
+
+ <li>
+ <a href="https://emscripten.org/docs/api_reference/module.html#module">Emscripten: 生成される JavaScript の API</a>
+ </li>
+ </ul>
+ </section>
+ </div>
+ </article>
+ </main>
+ <footer class="footer">
+ &copy; 2021 nsfisis
+ </footer>
+ </body>
+</html>
diff --git a/vhosts/blog/public/posts/index.html b/vhosts/blog/public/posts/index.html
index 4f75183b..a7028125 100644
--- a/vhosts/blog/public/posts/index.html
+++ b/vhosts/blog/public/posts/index.html
@@ -37,6 +37,21 @@
<h1>投稿一覧</h1>
</header>
<article class="post-entry">
+ <a href="/posts/2023-10-02/compile-php-runtime-to-wasm/">
+ <header class="entry-header">
+ <h2>【PHP】 PHP の処理系を Emscripten で WebAssembly にコンパイルする</h2>
+ </header>
+ <section class="entry-content">
+ <p>
+ PHP の処理系 (php/php-src) を Emscripten で WebAssembly にコンパイルし、任意のコードを隔離された環境で評価できるようにした。
+ </p>
+ </section>
+ <footer class="entry-footer">
+ <time datetime="2023-10-02">2023-10-02</time> 投稿
+ </footer>
+ </a>
+ </article>
+ <article class="post-entry">
<a href="/posts/2023-06-25/phpconfuk-2023-report/">
<header class="entry-header">
<h2>PHP カンファレンス福岡 2023 参加レポ</h2>
diff --git a/vhosts/blog/public/tags/index.html b/vhosts/blog/public/tags/index.html
index 03c67e55..ee066677 100644
--- a/vhosts/blog/public/tags/index.html
+++ b/vhosts/blog/public/tags/index.html
@@ -141,6 +141,13 @@
</header>
</a>
</article>
+ <article class="post-entry">
+ <a href="/tags/wasm/">
+ <header class="entry-header">
+ <h2>WebAssembly</h2>
+ </header>
+ </a>
+ </article>
</main>
<footer class="footer">
&copy; 2021 nsfisis
diff --git a/vhosts/blog/public/tags/php/index.html b/vhosts/blog/public/tags/php/index.html
index 3d217d70..60e83a63 100644
--- a/vhosts/blog/public/tags/php/index.html
+++ b/vhosts/blog/public/tags/php/index.html
@@ -38,6 +38,21 @@
<h1>タグ「PHP」一覧</h1>
</header>
<article class="post-entry">
+ <a href="/posts/2023-10-02/compile-php-runtime-to-wasm/">
+ <header class="entry-header">
+ <h2>【PHP】 PHP の処理系を Emscripten で WebAssembly にコンパイルする</h2>
+ </header>
+ <section class="entry-content">
+ <p>
+ PHP の処理系 (php/php-src) を Emscripten で WebAssembly にコンパイルし、任意のコードを隔離された環境で評価できるようにした。
+ </p>
+ </section>
+ <footer class="entry-footer">
+ <time datetime="2023-10-02">2023-10-02</time> 投稿
+ </footer>
+ </a>
+ </article>
+ <article class="post-entry">
<a href="/slides/2023-08-24/phpstudy-tokyo-155/">
<header class="entry-header">
<h2>登壇: PHP 勉強会@東京 第155 回 (LT)</h2>
diff --git a/vhosts/blog/public/tags/wasm/index.html b/vhosts/blog/public/tags/wasm/index.html
new file mode 100644
index 00000000..aa713a68
--- /dev/null
+++ b/vhosts/blog/public/tags/wasm/index.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html lang="ja-JP">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="author" content="nsfisis">
+ <meta name="copyright" content="&copy; 2023 nsfisis">
+ <meta name="description" content="タグ「WebAssembly」のついた記事またはスライドの一覧">
+ <meta name="keywords" content="WebAssembly">
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+ <title>タグ「WebAssembly」一覧 | REPL: Rest-Eat-Program Loop</title>
+ <link rel="stylesheet" href="/style.css?h=37fff6a2f0eef473abde58e55f28ea69">
+ </head>
+ <body class="list">
+ <header class="header">
+ <nav class="nav">
+ <ul>
+ <li>
+ <a href="/">REPL: Rest-Eat-Program Loop</a>
+ </li>
+ <li>
+ <a href="/about/">About</a>
+ </li>
+ <li>
+ <a href="/posts/">Posts</a>
+ </li>
+ <li>
+ <a href="/slides/">Slides</a>
+ </li>
+ <li>
+ <a href="/tags/">Tags</a>
+ </li>
+ </ul>
+ </nav>
+ </header>
+ <main class="main">
+ <header class="page-header">
+ <h1>タグ「WebAssembly」一覧</h1>
+ </header>
+ <article class="post-entry">
+ <a href="/posts/2023-10-02/compile-php-runtime-to-wasm/">
+ <header class="entry-header">
+ <h2>【PHP】 PHP の処理系を Emscripten で WebAssembly にコンパイルする</h2>
+ </header>
+ <section class="entry-content">
+ <p>
+ PHP の処理系 (php/php-src) を Emscripten で WebAssembly にコンパイルし、任意のコードを隔離された環境で評価できるようにした。
+ </p>
+ </section>
+ <footer class="entry-footer">
+ <time datetime="2023-10-02">2023-10-02</time> 投稿
+ </footer>
+ </a>
+ </article>
+ </main>
+ <footer class="footer">
+ &copy; 2021 nsfisis
+ </footer>
+ </body>
+</html>