summaryrefslogtreecommitdiffhomepage
path: root/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html
blob: 933253a31e1d87d0bf55a181af123a47657cf43d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
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=78b3f65931f3074e45f913f1f407a26d">
  </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>
            <h2 id="changelog">更新履歴</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" language="javascript">
              <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" language="c">
                <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">
                <div class="admonition-label">
                  NOTE
                </div>
                <div class="admonition-content">
                  <p>
                    <strong><strong>2025-04-23 追記</strong></strong>:
                  </p>
                  <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" language="javascript">
                    <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" language="dockerfile">
                <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" language="dockerfile">
                <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" language="dockerfile">
                <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" language="dockerfile">
                <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" language="dockerfile">
                <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" language="dockerfile">
                <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" language="dockerfile">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">$ docker build -t php-wasm .</span></span>
<span class="line"><span style="color:#24292E">$ echo </span><span style="color:#032F62">'echo "Hello, World!", PHP_EOL;'</span><span style="color:#24292E"> | docker run --rm -i php-wasm</span></span>
<span class="line"><span style="color:#24292E">Hello, World!</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E">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>