summaryrefslogtreecommitdiffhomepage
path: root/services/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-06-27 23:39:31 +0900
committernsfisis <nsfisis@gmail.com>2025-06-27 23:39:31 +0900
commit674fe965550444db87edc7937ff6932e1a918d9d (patch)
treee8a80dd958d3e082485286bf5785a7992b6e6b0e /services/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm
parentfe4d1d625b53796c5f20399790e5ff8c7a7e1608 (diff)
downloadnsfisis.dev-674fe965550444db87edc7937ff6932e1a918d9d.tar.gz
nsfisis.dev-674fe965550444db87edc7937ff6932e1a918d9d.tar.zst
nsfisis.dev-674fe965550444db87edc7937ff6932e1a918d9d.zip
feat(meta): rename vhosts/ directory to services/
Diffstat (limited to 'services/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm')
-rw-r--r--services/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html401
1 files changed, 401 insertions, 0 deletions
diff --git a/services/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html b/services/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html
new file mode 100644
index 00000000..34070bf9
--- /dev/null
+++ b/services/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html
@@ -0,0 +1,401 @@
+<!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">
+ <meta property="og:type" content="article">
+ <meta property="og:title" content="PHP の処理系を Emscripten で WebAssembly にコンパイルする|REPL: Rest-Eat-Program Loop">
+ <meta property="og:description" content="PHP の処理系 (php/php-src) を Emscripten で WebAssembly にコンパイルし、任意のコードを隔離された環境で評価できるようにした。">
+ <meta property="og:site_name" content="REPL: Rest-Eat-Program Loop">
+ <meta property="og:locale" content="ja_JP">
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+ <title>PHP の処理系を Emscripten で WebAssembly にコンパイルする|REPL: Rest-Eat-Program Loop</title>
+ <link rel="stylesheet" href="/style.css?h=9513229b52eb2041b99ba1b959305633">
+ </head>
+ <body class="single">
+ <header class="header">
+ <div class="site-logo">
+ <a href="/">REPL: Rest-Eat-Program Loop</a>
+ </div>
+ <nav class="nav">
+ <ul>
+ <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 の処理系を 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 id="changelog">
+ <h2><a href="#changelog">更新履歴</a></h2>
+ <ol>
+ <li class="revision">
+ <time datetime="2023-10-02">2023-10-02</time>: 公開
+ </li>
+ <li class="revision">
+ <time datetime="2025-04-23">2025-04-23</time>: fflush() の前に改行の出力が必要だった理由と正しい実装について追記
+ </li>
+ </ol>
+ </section>
+ <section id="section--intro">
+ <h2><a href="#section--intro">はじめに</a></h2>
+ <p>
+ <a href="https://emscripten.org/" rel="noreferrer" target="_blank">Emscripten</a> を用いて <a href="https://github.com/php/php-src" rel="noreferrer" target="_blank">PHP の処理系</a> を <a href="https://developer.mozilla.org/docs/WebAssembly" rel="noreferrer" target="_blank">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>
+ <div class="codeblock">
+ <div class="filename">
+ index.mjs
+ </div>
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> { readFile } </span><span style="color:#D73A49">from</span><span style="color:#032F62"> 'node:fs/promises'</span><span style="color:#24292E">;</span></span>
+<span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> PHPWasm </span><span style="color:#D73A49">from</span><span style="color:#032F62"> './php-wasm.mjs'</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> code</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#6F42C1"> readFile</span><span style="color:#24292E">(</span><span style="color:#032F62">'/dev/stdin'</span><span style="color:#24292E">, { encoding: </span><span style="color:#032F62">'utf-8'</span><span style="color:#24292E"> });</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49">const</span><span style="color:#24292E"> { </span><span style="color:#005CC5">ccall</span><span style="color:#24292E"> } </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> await</span><span style="color:#6F42C1"> PHPWasm</span><span style="color:#24292E">();</span></span>
+<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> result</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> ccall</span><span style="color:#24292E">(</span></span>
+<span class="line"><span style="color:#032F62"> 'php_wasm_run'</span><span style="color:#24292E">,</span></span>
+<span class="line"><span style="color:#032F62"> 'number'</span><span style="color:#24292E">, [</span><span style="color:#032F62">'string'</span><span style="color:#24292E">],</span></span>
+<span class="line"><span style="color:#24292E"> [code],</span></span>
+<span class="line"><span style="color:#24292E">);</span></span>
+<span class="line"><span style="color:#24292E">console.</span><span style="color:#6F42C1">log</span><span style="color:#24292E">(</span><span style="color:#032F62">`exit code: ${</span><span style="color:#24292E">result</span><span style="color:#032F62">}`</span><span style="color:#24292E">);</span></span></code></pre>
+ </div>
+ <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>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">#include</span><span style="color:#032F62"> &#x3C;stdio.h></span></span>
+<span class="line"><span style="color:#D73A49">#include</span><span style="color:#032F62"> &#x3C;emscripten.h></span></span>
+<span class="line"><span style="color:#D73A49">#include</span><span style="color:#032F62"> &#x3C;Zend/zend_execute.h></span></span>
+<span class="line"><span style="color:#D73A49">#include</span><span style="color:#032F62"> &#x3C;sapi/embed/php_embed.h></span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49">int</span><span style="color:#24292E"> EMSCRIPTEN_KEEPALIVE </span><span style="color:#6F42C1">php_wasm_run</span><span style="color:#24292E">(</span><span style="color:#D73A49">const</span><span style="color:#D73A49"> char*</span><span style="color:#E36209"> code</span><span style="color:#24292E">) {</span></span>
+<span class="line"><span style="color:#24292E"> zend_result result;</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49"> int</span><span style="color:#24292E"> argc </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> 1</span><span style="color:#24292E">;</span></span>
+<span class="line"><span style="color:#D73A49"> char*</span><span style="color:#24292E"> argv</span><span style="color:#D73A49">[]</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> { </span><span style="color:#032F62">"php.wasm"</span><span style="color:#24292E">, </span><span style="color:#005CC5">NULL</span><span style="color:#24292E"> };</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#6F42C1"> PHP_EMBED_START_BLOCK</span><span style="color:#24292E">(argc, argv);</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#24292E"> result </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> zend_eval_string_ex</span><span style="color:#24292E">(code, </span><span style="color:#005CC5">NULL</span><span style="color:#24292E">, </span><span style="color:#032F62">"php.wasm code"</span><span style="color:#24292E">, </span><span style="color:#005CC5">1</span><span style="color:#24292E">);</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#6F42C1"> PHP_EMBED_END_BLOCK</span><span style="color:#24292E">();</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#6F42C1"> fprintf</span><span style="color:#24292E">(stdout, </span><span style="color:#032F62">"</span><span style="color:#005CC5">\n</span><span style="color:#032F62">"</span><span style="color:#24292E">);</span></span>
+<span class="line"><span style="color:#6F42C1"> fflush</span><span style="color:#24292E">(stdout);</span></span>
+<span class="line"><span style="color:#6F42C1"> fprintf</span><span style="color:#24292E">(stderr, </span><span style="color:#032F62">"</span><span style="color:#005CC5">\n</span><span style="color:#032F62">"</span><span style="color:#24292E">);</span></span>
+<span class="line"><span style="color:#6F42C1"> fflush</span><span style="color:#24292E">(stderr);</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49"> return</span><span style="color:#24292E"> result </span><span style="color:#D73A49">==</span><span style="color:#24292E"> SUCCESS </span><span style="color:#D73A49">?</span><span style="color:#005CC5"> 0</span><span style="color:#D73A49"> :</span><span style="color:#005CC5"> 1</span><span style="color:#24292E">;</span></span>
+<span class="line"><span style="color:#24292E">}</span></span></code></pre>
+ </div>
+ <p>
+ ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。
+ </p>
+ <p>
+ まずは <code>EMSCRIPTEN_KEEPALIVE</code> について。これは Emscripten が用意している特殊なマクロである。このマクロが付与されている関数は、どこからも使用されていなくともコンパイル後の WebAssembly バイナリから削除されない。もしこれを付け忘れると、未使用の関数とみなされ削除される。
+ </p>
+ <p>
+ 次に、コードを評価したあとに呼んでいる標準出力と標準エラー出力に対する改行の出力について。出力バッファから出力させるためだけなら改行を出力させなくとも <code>fflush()</code> だけで事足りると考えたのだが、ないと動かなかったので追加した。これにより、PHP コードの出力の後ろに余分な改行が追加されてしまう。改行を出力せずともバッファを消費させる手段をご存知のかたはご教示願いたい。
+ </p>
+ <div class="admonition" editat="2025-04-23" operation="追記">
+ <div class="admonition-label">
+ 2025-04-23 追記
+ </div>
+ <div class="admonition-content">
+ <p>
+ <code>fflush()</code> の前に改行の出力が必要だった理由が判明したので追記する。これは、<code>index.mjs</code> で標準出力・標準エラー出力へ出力する方法を指定せず、デフォルトの実装に任せているため。Emscripten のデフォルト実装では、改行コードを出力するまで出力内容がバッファリングされ、<code>fflush()</code> が機能しない。
+ </p>
+ <p>
+ デフォルトの出力方法は <code>index.mjs</code> の中で <code>PHPWasm()</code> を呼ぶとき、<code>stdout</code>・<code>stderr</code> というオプションを渡せば変更できる。
+ </p>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#24292E"> { </span><span style="color:#005CC5">ccall</span><span style="color:#24292E"> } </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> await</span><span style="color:#6F42C1"> PHPWasm</span><span style="color:#24292E">({</span></span>
+<span class="line"><span style="color:#6F42C1"> stdout</span><span style="color:#24292E">: (</span><span style="color:#E36209">c</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {</span></span>
+<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (c </span><span style="color:#D73A49">===</span><span style="color:#005CC5"> null</span><span style="color:#24292E">) {</span></span>
+<span class="line"><span style="color:#6A737D"> // flush the standard output.</span></span>
+<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">else</span><span style="color:#24292E"> {</span></span>
+<span class="line"><span style="color:#6A737D"> // output c to the standard output.</span></span>
+<span class="line"><span style="color:#24292E"> }</span></span>
+<span class="line"><span style="color:#24292E"> },</span></span>
+<span class="line"><span style="color:#24292E">});</span></span></code></pre>
+ </div>
+ <p>
+ <code>c</code> は <code>null</code> か 1バイト符号つき整数を取り、<code>null</code> が flush 要求を意味する。
+ </p>
+ <p>
+ 記事末尾のリポジトリはすでにこの変更を適用済み。<code>stdout</code> や <code>stderr</code> の完全なサンプルはそちらを参照のこと。
+ </p>
+ </div>
+ </div>
+ </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" rel="noreferrer" target="_blank">Emscripten 公式が提供している Docker イメージ</a> を使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
+ </p>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">FROM</span><span style="color:#24292E"> emscripten/emsdk:3.1.46 </span><span style="color:#D73A49">AS</span><span style="color:#24292E"> wasm-builder</span></span></code></pre>
+ </div>
+ <p>
+ 次に、 <a href="https://github.com/php/php-src" rel="noreferrer" target="_blank">php/php-src</a> から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
+ </p>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">RUN</span><span style="color:#24292E"> git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49">RUN</span><span style="color:#24292E"> apt-get update &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> apt-get install -y --no-install-recommends \</span></span>
+<span class="line"><span style="color:#24292E"> autoconf \</span></span>
+<span class="line"><span style="color:#24292E"> bison \</span></span>
+<span class="line"><span style="color:#24292E"> pkg-config \</span></span>
+<span class="line"><span style="color:#24292E"> re2c \</span></span>
+<span class="line"><span style="color:#24292E"> &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> :</span></span></code></pre>
+ </div>
+ <p>
+ 続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
+ </p>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">RUN</span><span style="color:#24292E"> cd php-src &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> ./buildconf --force &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> emconfigure ./configure \</span></span>
+<span class="line"><span style="color:#24292E"> --disable-all \</span></span>
+<span class="line"><span style="color:#24292E"> --disable-mbregex \</span></span>
+<span class="line"><span style="color:#24292E"> --disable-fiber-asm \</span></span>
+<span class="line"><span style="color:#24292E"> --disable-cli \</span></span>
+<span class="line"><span style="color:#24292E"> --disable-cgi \</span></span>
+<span class="line"><span style="color:#24292E"> --disable-phpdbg \</span></span>
+<span class="line"><span style="color:#24292E"> --enable-embed=static \</span></span>
+<span class="line"><span style="color:#24292E"> --enable-mbstring \</span></span>
+<span class="line"><span style="color:#24292E"> --without-iconv \</span></span>
+<span class="line"><span style="color:#24292E"> --without-libxml \</span></span>
+<span class="line"><span style="color:#24292E"> --without-pcre-jit \</span></span>
+<span class="line"><span style="color:#24292E"> --without-pdo-sqlite \</span></span>
+<span class="line"><span style="color:#24292E"> --without-sqlite3 \</span></span>
+<span class="line"><span style="color:#24292E"> &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> EMCC_CFLAGS=</span><span style="color:#032F62">'-s ERROR_ON_UNDEFINED_SYMBOLS=0'</span><span style="color:#24292E"> emmake make -j$(nproc) &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> mv libs/libphp.a .. &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> make clean &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> git clean -fd &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> :</span></span></code></pre>
+ </div>
+ <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>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">COPY</span><span style="color:#24292E"> php-wasm.c /src/</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49">RUN</span><span style="color:#24292E"> cd php-src &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> emcc \</span></span>
+<span class="line"><span style="color:#24292E"> -c \</span></span>
+<span class="line"><span style="color:#24292E"> -o php-wasm.o \</span></span>
+<span class="line"><span style="color:#24292E"> -I . \</span></span>
+<span class="line"><span style="color:#24292E"> -I TSRM \</span></span>
+<span class="line"><span style="color:#24292E"> -I Zend \</span></span>
+<span class="line"><span style="color:#24292E"> -I main \</span></span>
+<span class="line"><span style="color:#24292E"> ../php-wasm.c \</span></span>
+<span class="line"><span style="color:#24292E"> &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> mv php-wasm.o .. &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> make clean &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> git clean -fd &#x26;&#x26; \</span></span>
+<span class="line"><span style="color:#24292E"> :</span></span></code></pre>
+ </div>
+ <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>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">RUN</span><span style="color:#24292E"> emcc \</span></span>
+<span class="line"><span style="color:#24292E"> -s ENVIRONMENT=node \</span></span>
+<span class="line"><span style="color:#24292E"> -s ERROR_ON_UNDEFINED_SYMBOLS=0 \</span></span>
+<span class="line"><span style="color:#24292E"> -s EXPORTED_RUNTIME_METHODS=</span><span style="color:#032F62">'["ccall"]'</span><span style="color:#24292E"> \</span></span>
+<span class="line"><span style="color:#24292E"> -s EXPORT_ES6=1 \</span></span>
+<span class="line"><span style="color:#24292E"> -s INITIAL_MEMORY=16777216 \</span></span>
+<span class="line"><span style="color:#24292E"> -s INVOKE_RUN=0 \</span></span>
+<span class="line"><span style="color:#24292E"> -s MODULARIZE=1 \</span></span>
+<span class="line"><span style="color:#24292E"> -o php-wasm.js \</span></span>
+<span class="line"><span style="color:#24292E"> php-wasm.o \</span></span>
+<span class="line"><span style="color:#24292E"> libphp.a \</span></span>
+<span class="line"><span style="color:#24292E"> ;</span></span></code></pre>
+ </div>
+ <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>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">FROM</span><span style="color:#24292E"> node:20.7</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49">WORKDIR</span><span style="color:#24292E"> /app</span></span>
+<span class="line"><span style="color:#D73A49">COPY</span><span style="color:#24292E"> --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs</span></span>
+<span class="line"><span style="color:#D73A49">COPY</span><span style="color:#24292E"> --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm</span></span>
+<span class="line"><span style="color:#D73A49">COPY</span><span style="color:#24292E"> index.mjs /app/</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#D73A49">ENTRYPOINT</span><span style="color:#24292E"> [</span><span style="color:#032F62">"node"</span><span style="color:#24292E">, </span><span style="color:#032F62">"index.mjs"</span><span style="color:#24292E">]</span></span></code></pre>
+ </div>
+ </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>
+ <div class="codeblock">
+ <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span>$ docker build -t php-wasm .</span></span>
+<span class="line"><span>$ echo 'echo "Hello, World!", PHP_EOL;' | docker run --rm -i php-wasm</span></span>
+<span class="line"><span>Hello, World!</span></span>
+<span class="line"><span></span></span>
+<span class="line"><span></span></span>
+<span class="line"><span>exit code: 0</span></span></code></pre>
+ </div>
+ </section>
+ <section id="section--outro">
+ <h2><a href="#section--outro">まとめ</a></h2>
+ <p>
+ <a href="https://github.com/nsfisis/tiny-php.wasm" rel="noreferrer" target="_blank">ここまでをまとめた 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" rel="noreferrer" target="_blank">php/php-src: ビルドの方法について</a>
+ </li>
+ <li>
+ <a href="https://emscripten.org/docs/getting_started/Tutorial.html" rel="noreferrer" target="_blank">Emscripten: チュートリアル</a>
+ </li>
+ <li>
+ <a href="https://emscripten.org/docs/compiling/Building-Projects.html#building-projects" rel="noreferrer" target="_blank">Emscripten: ビルドの基本</a>
+ </li>
+ <li>
+ <a href="https://emscripten.org/docs/tools_reference/emcc.html#emccdoc" rel="noreferrer" target="_blank">Emscripten: <code>emcc</code> などのリファレンス</a>
+ </li>
+ <li>
+ <a href="https://emscripten.org/docs/api_reference/module.html#module" rel="noreferrer" target="_blank">Emscripten: 生成される JavaScript の API</a>
+ </li>
+ </ul>
+ </section>
+ </div>
+ </article>
+ </main>
+ <footer class="footer">
+ &copy; 2021 nsfisis
+ </footer>
+ </body>
+</html>